O Registro do Windows é um banco de dados hierárquico de configurações que o Windows e os softwares instalados nele leem e gravam em tempo de execução. Em C#, chegamos a ele por meio de dois tipos do namespace Microsoft.Win32: Registry para um único valor e RegistryKey para todo o resto.

Registry.GetValue() e Registry.SetValue() recebem o caminho completo de uma chave e fazem uma leitura ou uma gravação. RegistryKey representa uma chave aberta, por isso pode enumerar, excluir, inspecionar tipos de valor e definir permissões, e precisa ser descartado.

O registro só existe no Windows, então toda chamada fica protegida por uma verificação. OperatingSystem.IsWindows() é essa verificação e, sem ela, o compilador emite CA1416 no código que lançaria uma exceção em qualquer outra plataforma.

Para baixar o código-fonte deste artigo, você pode acessar nosso repositório no GitHub.

O que é o Registro do Windows?

O Registro do Windows é um banco de dados hierárquico que o Windows, os drivers de dispositivo e as aplicações instaladas usam para armazenar configurações como pares nome/valor.

A estrutura do registro é uma árvore. Um nó dessa árvore é uma chave; uma chave contém subchaves e também contém valores, cada um formado por um nome associado a dados e a um tipo declarado. A comparação mais fácil de entender é com um sistema de arquivos: as chaves se comportam como diretórios, e os valores, como os arquivos dentro deles.

A árvore tem um conjunto fixo de raízes. O Windows as mantém abertas para nós, e elas se chamam chaves predefinidas, chaves raiz ou chaves base.

Duas dessas raízes fazem quase todo o trabalho. HKEY_CURRENT_USER guarda as configurações da conta sob a qual o nosso processo está rodando. HKEY_LOCAL_MACHINE guarda as configurações compartilhadas por todas as contas do computador.

Essa divisão é o que importa na prática. Por padrão, gravar em HKEY_CURRENT_USER não exige nenhum direito especial, enquanto gravar em HKEY_LOCAL_MACHINE exige um processo com privilégios elevados. É a primeira coisa que quebra.

Quando essas gravações falham, a solução é executar a aplicação como administrador.

Estrutura do Registro do Windows

O Registro do Windows usa uma estrutura de árvore para os dados. Um nó dessa árvore é uma chave, e a referência da Microsoft sobre o registro do Win32 diz o resto com todas as letras: “Cada chave pode conter tanto subchaves quanto entradas de dados chamadas valores.” Além disso, podemos considerar o Registro do Windows um banco de dados de pares nome/valor. Outra forma de enxergar o Registro do Windows é como uma estrutura de diretórios. Basicamente, a chave representa o diretório, e o conteúdo do diretório são subdiretórios (subchaves) ou arquivos (pares nome/valor).

O Registro do Windows vem com um conjunto de chaves predefinidas. Uma aplicação pode usar identificadores (handles) para acessar essas chaves. As chaves predefinidas estão sempre abertas e servem como pontos de entrada para o registro. Versões diferentes do Windows podem ter conjuntos diferentes de chaves predefinidas. Também chamamos essas chaves de elementos raiz, chaves raiz ou chaves base. Estas são as que a classe Registry expõe:

Chave baseCampo de RegistryMembro de RegistryHiveO que contém
HKEY_CLASSES_ROOTRegistry.ClassesRootRegistryHive.ClassesRootAssociações de extensões de nomes de arquivo e dados de registro de classes COM.
HKEY_CURRENT_USERRegistry.CurrentUserRegistryHive.CurrentUserConfigurações da conta que executa o nosso processo. Por padrão, aceita gravação sem privilégios elevados.
HKEY_LOCAL_MACHINERegistry.LocalMachineRegistryHive.LocalMachineConfigurações de toda a máquina. Por padrão, gravar exige um processo com privilégios elevados.
HKEY_USERSRegistry.UsersRegistryHive.UsersOs perfis de usuário carregados na máquina no momento.
HKEY_CURRENT_CONFIGRegistry.CurrentConfigRegistryHive.CurrentConfigDiferenças entre o perfil de hardware atual e o padrão.
HKEY_PERFORMANCE_DATARegistry.PerformanceDataRegistryHive.PerformanceDataDados de contadores de desempenho, montados sob demanda em vez de armazenados em disco.

O Win32 define mais algumas chaves predefinidas que a classe Registry não expõe, entre elas HKEY_CURRENT_USER_LOCAL_SETTINGS. Ainda assim, podemos chegar a elas passando para RegistryKey.FromHandle() o identificador predefinido de cada uma, encapsulado em um SafeRegistryHandle.

Para saber mais sobre o assunto, leia sobre as chaves predefinidas.

Valores das chaves do Registro do Windows

Os valores das chaves do Registro do Windows podem ser de tipos diferentes:

Tipo no registroMembro de RegistryValueKindRetornado por GetValue() comoObservações
REG_SZStringstringUma string terminada por caractere nulo. É o padrão de SetValue() com uma string.
REG_EXPAND_SZExpandStringstringContém referências não expandidas, como %PATH%. GetValue() as expande, a menos que passemos RegistryValueOptions.DoNotExpandEnvironmentNames.
REG_BINARYBinarybyte[]Quaisquer dados em formato binário.
REG_DWORDDWordintUm número de 32 bits. REG_DWORD_LITTLE_ENDIAN é o mesmo tipo com outro nome.
REG_QWORDQWordlongUm número de 64 bits.
REG_MULTI_SZMultiStringstring[]Um array de strings terminadas por caractere nulo.
REG_NONENonebyte[]O tipo do valor não está definido.
REG_DWORD_BIG_ENDIAN, REG_LINK, REG_RESOURCE_LISTUnknownbyte[] para REG_DWORD_BIG_ENDIAN, o valor padrão que passamos para os outros doisRegistryValueKind não tem membro para esses tipos, então GetValueKind() informa Unknown.

Quando o sistema operacional inicia ou um novo usuário faz login, o novo conjunto de chaves do registro é carregado na memória. Esse conjunto depende do perfil do usuário, da configuração e das preferências. Um conjunto assim se chama hive. Um hive não é algo que uma aplicação cria ao adicionar uma chave.

Abrir o Registro do Windows

AVISO! Vamos mostrar como abrir e examinar o Registro do Windows. Mais adiante, também vamos fazer algumas alterações no Registro do Windows por meio de código. O leitor assume inteira responsabilidade por todas as alterações involuntárias feitas no próprio sistema e por possíveis problemas causados ao modificar ou excluir chaves do registro manualmente ou ao executar a aplicação de demonstração. É altamente recomendável fazer uma cópia de segurança do Registro do Windows antes de qualquer alteração.

Podemos abrir o Registro do Windows de duas formas:

  • Digite regedit na caixa de pesquisa da barra de tarefas e selecione a aplicação Registry Editor.
  • Clique com o botão direito no botão Start do Windows, selecione Run, digite regedit na caixa e clique em OK.

O Registro do Windows no computador do usuário pode ter uma aparência um pouco diferente, mas a imagem a seguir mostra a estrutura básica dele:

Estrutura do Registro do Windows

HKEY_CLASSES_ROOT é uma chave base, uma visão combinada das chaves Classes que ficam em HKEY_LOCAL_MACHINE e HKEY_CURRENT_USER, e não um hive.

Como acessar o Registro do Windows no .NET?

Quase tudo de que precisamos está no namespace Microsoft.Win32, que já vem na biblioteca de classes base. Não há pacote NuGet para instalar nem referência para adicionar.

Dois tipos concentram toda a API. Registry é uma classe estática que expõe as seis chaves base como campos e acrescenta exatamente dois métodos, GetValue() e SetValue(), e cada um deles recebe o caminho completo de uma chave.

RegistryKey é uma classe não estática que representa uma chave aberta e reúne todo o resto: abertura, criação, exclusão, enumeração, leitura dos tipos de valor e controle de acesso. Ela implementa IDisposable porque cada instância mantém um identificador aberto para uma chave nativa.

O registro é um recurso do Windows, então toda a API é anotada como exclusiva do Windows. Em um framework de destino que não é específico do Windows, o compilador emite CA1416 sempre que não consegue provar que um ponto de chamada roda no Windows.

Proteger a chamada com OperatingSystem.IsWindows() satisfaz o analisador e mantém o resto da aplicação multiplataforma.

As classes Registry e RegistryKey são as principais classes para interagir com o Registro do Windows.

A classe Registry

A classe Registry dá acesso às chaves raiz. Além disso, implementa métodos estáticos para ler ou gravar pares chave/valor. Todos os métodos e constantes deste artigo ficam dentro de uma public static class RegistryDemo, em um arquivo próprio, que começa com using Microsoft.Win32;. Por exemplo, podemos imprimir o nome de HKEY_CURRENT_USER. Esta primeira versão não tem, de propósito, a verificação de plataforma, para vermos o que o compilador diz sobre ela:

public static string GetCurrentUserRootKeyName()
{
    var registryKey = Registry.CurrentUser;
    return registryKey.Name;
}

No método acima, vemos avisos do compilador:

warning CA1416: This call site is reachable on all platforms. 'RegistryKey.Name' is only supported on: 'windows'.
warning CA1416: This call site is reachable on all platforms. 'Registry.CurrentUser' is only supported on: 'windows'.

O Registro do Windows é um recurso específico do Windows e não existe em outros sistemas operacionais.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

No entanto, o .NET é um framework multiplataforma. Por isso, o compilador nos avisa que esse código não roda fora de um ambiente Windows. É uma boa prática verificar em qual sistema operacional o código está rodando e executar essa parte do código de acordo com o resultado:

public static string GetCurrentUserRootKeyNameWithPlatformCheck()
{
    if (!OperatingSystem.IsWindows())
    {
        return "Unsupported platform";
    }

    return Registry.CurrentUser.Name;
}

A classe RegistryKey

A classe RegistryKey encapsula o nó do Registro do Windows no nível de chave. Ela oferece os métodos para trabalhar com a chave e seus pares nome/valor, inspecionar certas propriedades da chave do registro e obter ou definir o controle de acesso sobre ela. Por exemplo, podemos contar o número de subchaves de uma chave específica:

public static int GetCurrentUserRootKeySubkeyCount()
{
    if (!OperatingSystem.IsWindows())
    {
        return -1;
    }

    return Registry.CurrentUser.SubKeyCount;
}

Vamos ver outros métodos e propriedades da classe RegistryKey nas próximas seções.

Casos de uso típicos do Registro do Windows

Os casos de uso mais comuns do Registro do Windows incluem:

  • Configurações do sistema: configuração e comportamento do próprio Windows
  • Configuração de hardware: drivers de dispositivo e configurações de hardware
  • Configuração de software: preferências, informações de licença, tamanho e posição de janelas e outros dados específicos de cada aplicação
  • Perfis de usuário: configurações e preferências específicas de cada usuário, que permitem que os usuários tenham interfaces personalizadas
  • Configurações de segurança: políticas de segurança e de grupo, configurações de contas de usuário
  • Associações de arquivos: configurações específicas de cada tipo de arquivo, que permitem que as aplicações abram arquivos com determinadas extensões
  • Configuração de inicialização: informações sobre programas que devem iniciar automaticamente com o Windows

As preferências da aplicação são o único item dessa lista que tem uma alternativa portável: o sistema de configuração do .NET lê as mesmas configurações sem prender a aplicação ao Windows. O registro ainda tem seu lugar quando a configuração pertence à máquina, e não a uma implantação, o que muitas vezes acontece com uma aplicação executada como serviço do Windows.

Como gravar e ler um valor do registro em C#?

Gravar um valor exige uma única chamada. Registry.SetValue() recebe o caminho completo da chave, o nome do valor e os dados, e cria a chave se ela ainda não existir.

Para lê-lo de volta, usamos a chamada correspondente. Registry.GetValue() recebe o mesmo caminho, o mesmo nome de valor e um valor padrão a retornar quando esse nome não existe. O resultado vem como object, então nós mesmos fazemos a conversão.

O segundo parâmetro é o que confunde muita gente. Ele nomeia um valor dentro da chave, e não mais um nível no caminho da chave. No nosso exemplo, HKEY_CURRENT_USER\CodeMazeRegistryDemoSubKey é a chave e CodeMazeRegistryDemoName é um valor dentro dela.

Dois casos diferentes de valor não encontrado se parecem. GetValue() retorna null quando nem sequer encontra a chave, e retorna o valor padrão que passamos quando a chave existe, mas não tem nenhum valor com esse nome.

Toda chave também pode ter um valor padrão sem nome, que lemos e gravamos passando null ou uma string vazia como nome.

Nos trechos de código a seguir, vamos usar algumas constantes:

private const string CodeMazeRegistryDemoSubKey = "CodeMazeRegistryDemoSubKey";
private const string CodeMazeRegistryDemoName = "CodeMazeRegistryDemoName";
public const string CodeMazeRegistryDemoValue = "CodeMazeRegistryDemoValue";

CodeMazeRegistryDemoSubKey é o nome da subchave com que vamos trabalhar, e CodeMazeRegistryDemoName é o nome do valor na chave. Por fim, CodeMazeRegistryDemoValue é o valor que vamos gravar no par nome/valor. Como exemplo, vamos usar a chave base Registry.CurrentUser (HKEY_CURRENT_USER).

Ler e gravar no Registro do Windows com a classe Registry

A classe Registry implementa os métodos estáticos GetValue() e SetValue(). Vejamos como usá-los:

public static string ReadAndWriteRegistryValueUsingRegistryClass()
{
    if (!OperatingSystem.IsWindows())
    {
        return string.Empty;
    }

    var subKeyToWrite = $@"{Registry.CurrentUser.Name}\{CodeMazeRegistryDemoSubKey}";

    Registry.SetValue(subKeyToWrite, CodeMazeRegistryDemoName, CodeMazeRegistryDemoValue);
    var writtenValue = Registry.GetValue(subKeyToWrite, CodeMazeRegistryDemoName, string.Empty);

    Registry.CurrentUser.DeleteSubKey(CodeMazeRegistryDemoSubKey);

    return writtenValue?.ToString() ?? string.Empty;
}

O método SetValue() tem três parâmetros: o caminho completo da subchave, o nome e o valor que ele vai gravar no registro. Se a subchave não existir, ela será criada. Se a chave não existir, ela será criada. Caso contrário, o método grava o nome e o valor na chave especificada.

O método SetValue() tem mais uma sobrecarga, que permite especificar o tipo de dados do valor. O método abre e fecha a chave automaticamente ao gravar o valor nela.

Assim como o método SetValue(), GetValue() também tem três parâmetros: o caminho completo da subchave, o nome e o valor padrão que será retornado se o nome não existir. O método retorna null se não encontrar a subchave e retorna o valor padrão se o nome não existir. O valor retornado é do tipo object, e precisamos aplicar uma conversão explícita ou convertê-lo no tipo adequado.

Cada chave do registro pode conter um valor padrão sem nome, que o método GetValue() retorna se passarmos null ou string.Empty como nome. Podemos usar null ou string.Empty como argumento de nome no método SetValue() se quisermos gravar o valor sob o nome padrão.

Default value for Windows Registry Key

O nosso código grava o valor chamado CodeMazeRegistryDemoName; a captura de tela mostra outro nome.

Por fim, chamamos o método DeleteSubKey() da classe RegistryKey para excluir a chave que criamos:

Registry.CurrentUser.DeleteSubKey(CodeMazeRegistryDemoSubKey);

Ler e gravar no Registro do Windows com a classe RegistryKey

Como mencionamos antes, a classe RegistryKey representa um nó do Registro do Windows. Ela implementa vários métodos para trabalhar com as chaves do registro:

public static string ReadAndWriteRegistryValueUsingRegistryKeyClass()
{
    if (!OperatingSystem.IsWindows())
    {
        return string.Empty;
    }

    var baseKey = Registry.CurrentUser;

    using var subKey = baseKey.OpenSubKey(CodeMazeRegistryDemoSubKey, true) ??
                       baseKey.CreateSubKey(CodeMazeRegistryDemoSubKey);

    subKey.SetValue(CodeMazeRegistryDemoName, CodeMazeRegistryDemoValue);
    var writtenValue = subKey.GetValue(CodeMazeRegistryDemoName);
    subKey.DeleteValue(CodeMazeRegistryDemoName);

    baseKey.DeleteSubKey(CodeMazeRegistryDemoSubKey);

    return writtenValue?.ToString() ?? string.Empty;
}

Abrimos a subchave com o método OpenSubKey(). Esse método tem cinco sobrecargas, que tratam o controle de acesso de formas diferentes. No nosso exemplo, usamos a sobrecarga OpenSubKey(string, bool), com a qual podemos abrir a subchave com acesso de gravação. Se omitirmos esse parâmetro, a chave é aberta apenas com acesso de leitura, e o método SetValue() lança uma exceção. Se a chave especificada não existir, o método retorna null.

OpenSubKey(name) abre a chave somente para leitura, então qualquer gravação feita por meio dela lança uma UnauthorizedAccessException; OpenSubKey(name, true) e CreateSubKey(name) são duas formas de obter uma chave em que podemos gravar.

No nosso caso, a chave será sempre null, porque a excluímos com o método DeleteSubKey() no fim do método ReadAndWriteRegistryValueUsingRegistryKeyClass(). Por outro lado, se comentarmos a chamada ao método DeleteSubKey() e executarmos a aplicação de novo, a chave vai existir.

Na linha seguinte, criamos a chave chamando o método CreateSubKey(). Esse método abre a chave com acesso de gravação. Ele tem sete sobrecargas, que nos permitem criar a chave com diferentes controles de acesso e opções.

Com a chamada ao método SetValue() da classe RegistryKey, gravamos o par nome/valor na chave. Se o nome já existir, o valor é atualizado. Os parâmetros do método são o nome e o valor, já que o caminho da chave do registro já está encapsulado na variável subKey.

O método GetValue() lê o valor com o nome especificado. Além disso, chamamos em seguida o método DeleteValue(), que exclui o valor com o nome especificado. No entanto, esse passo é tecnicamente desnecessário, já que depois excluímos a subchave inteira chamando o método DeleteSubKey().

Por fim, retornamos o valor como string. Vale notar que a classe RegistryKey também tem um método ToString() que retorna o nome da chave como string.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Descarte do RegistryKey

A classe RegistryKey implementa a interface IDisposable, então tudo o que já sabemos sobre como gerenciar objetos IDisposable também vale aqui. No exemplo, envolvemos a subchave em uma declaração using, o que significa que o método Dispose() será chamado automaticamente.

Como alternativa, se não usarmos a declaração using, podemos chamar o método Dispose() diretamente, o que, consequentemente, fecha a chave, da mesma forma que uma chamada direta ao método Close(). A chamada ao método Close() libera o identificador da chave, e nenhuma outra gravação é possível.

Da mesma forma, podemos chamar o método Flush() se quisermos ter certeza de que as alterações na chave foram gravadas no disco, mas, nos casos típicos, isso é desnecessário, já que as alterações no registro são armazenadas automaticamente.

Registry ou RegistryKey: qual classe usar?

As duas classes leem e gravam valores. A diferença está em quantas vezes mexemos na chave.

Registry serve para acessos pontuais. Cada chamada a GetValue() ou SetValue() abre a chave, faz o trabalho e a fecha de novo, então ler uma única configuração é uma linha de código, sem nada para descartar.

RegistryKey serve para todo o resto. Abrimos ou criamos a chave uma vez, mantemos o identificador e chamamos nela quantos métodos forem necessários. Essa classe também é a única via para a enumeração, a leitura dos tipos de valor, a exclusão, o controle de acesso, as visões de 32 e de 64 bits e as máquinas remotas.

É por causa desse identificador que a escolha importa além da conveniência. RegistryKey implementa IDisposable, então deve ficar em uma declaração using, e Registry não tem nada para descartar.

Uma regra resolve a maioria dos casos. Gravar por meio de RegistryKey exige uma chave aberta para gravação, então chamamos OpenSubKey(name, true) ou CreateSubKey(name). OpenSubKey(name) com um único argumento abre a chave somente para leitura, e SetValue() nessa chave lança uma exceção.

TarefaRegistry (estática)RegistryKey (não estática)
Ler um valorRegistry.GetValue(path, name, default)key.GetValue(name)
Gravar um valorRegistry.SetValue(path, name, value)key.SetValue(name, value)
Abrir uma chave existentenão disponívelkey.OpenSubKey(name) ou OpenSubKey(name, writable)
Criar uma chaveacontece implicitamente dentro de SetValue()key.CreateSubKey(name)
Excluir um valornão disponívelkey.DeleteValue(name)
Excluir uma chavenão disponívelkey.DeleteSubKey(name), key.DeleteSubKeyTree(name)
Listar subchaves ou nomes de valoresnão disponívelkey.GetSubKeyNames(), key.GetValueNames()
Ler o tipo de um valornão disponívelkey.GetValueKind(name)
Ler ou definir o controle de acessonão disponívelkey.GetAccessControl(), key.SetAccessControl()
Escolher a visão de 32 ou de 64 bitsnão disponívelRegistryKey.OpenBaseKey(hive, view)
Acessar outra máquinanão disponívelRegistryKey.OpenRemoteBaseKey(hive, machine)
Precisa ser descartadonãosim, implementa IDisposable

A mesma regra vale para o resto das operações de gravação: CreateSubKey(), DeleteSubKey(), DeleteSubKeyTree() e DeleteValue() passam todos pela mesma verificação de acesso de gravação que SetValue().

O que mais podemos fazer com o Registro do Windows em C#?

A classe RegistryKey implementa métodos para um trabalho mais avançado com o Registro do Windows. Vejamos alguns deles.

Enumeração de chaves e valores

Podemos obter um array com todas as subchaves de uma chave específica chamando o método GetSubKeyNames():

public static string[] GetSubKeyNames()
{
    if (!OperatingSystem.IsWindows())
    {
        return [];
    }

    using var subKey = Registry.CurrentUser.CreateSubKey(CodeMazeRegistryDemoSubKey);
    subKey.CreateSubKey("SubKey1");
    subKey.CreateSubKey("SubKey2");

    var subKeyNames = subKey.GetSubKeyNames();

    Registry.CurrentUser.DeleteSubKeyTree(CodeMazeRegistryDemoSubKey);

    return subKeyNames;
}

Além disso, podemos obter um array com todos os nomes de valores chamando o método GetValueNames():

public static string[] GetValueNames()
{
    if (!OperatingSystem.IsWindows())
    {
        return [];
    }

    using var subKey = Registry.CurrentUser.CreateSubKey(CodeMazeRegistryDemoSubKey);
    using var subKey1 = subKey.CreateSubKey("SubKey1");
    subKey1.SetValue("Name1", "Value1");
    subKey1.SetValue("Name2", "Value2");

    var subKeyNames = subKey1.GetValueNames();

    Registry.CurrentUser.DeleteSubKeyTree(CodeMazeRegistryDemoSubKey);

    return subKeyNames;
}

CreateSubKey() retorna uma chave, nunca null, então não há operador condicional nulo nem array de reserva em nenhum dos dois métodos.

Obter o tipo de dados do valor de uma chave do registro

A classe RegistryKey implementa o método GetValueKind(), que retorna o tipo de dados do valor com o nome especificado:

public static string GetValueKind()
{
    if (!OperatingSystem.IsWindows())
    {
        return string.Empty;
    }

    using var subKey = Registry.CurrentUser.CreateSubKey(CodeMazeRegistryDemoSubKey);
    using var subKey1 = subKey.CreateSubKey("SubKey1");
    subKey1.SetValue("Name1", "Value1");

    var valueKind = subKey1.GetValueKind("Name1");

    Registry.CurrentUser.DeleteSubKeyTree(CodeMazeRegistryDemoSubKey);

    return valueKind.ToString();
}

O método GetValueKind() retorna um membro da enumeração RegistryValueKind, definida no namespace Microsoft.Win32.

Permissões de acesso à chave do registro

Os métodos CreateSubKey() e OpenSubKey() têm várias sobrecargas que aceitam parâmetros para controlar com precisão os direitos de acesso à chave do registro.

Com a enumeração RegistryKeyPermissionCheck, podemos impor as verificações de segurança ao abrir a chave e acessar os valores dela. Além disso, com a enumeração RegistryRights, conseguimos um controle de acesso detalhado sobre a chave do registro, depois de adicionarmos using System.Security.AccessControl; e using System.Security.Principal; no início do arquivo:

public static bool SetRegistryKeyAccessPermissions()
{
    if (!OperatingSystem.IsWindows())
    {
        return false;
    }

    var user = $@"{Environment.UserDomainName}\{Environment.UserName}";
    var registrySecurity = new RegistrySecurity();

    var accessRule = new RegistryAccessRule(user,
        RegistryRights.ReadKey | RegistryRights.WriteKey | RegistryRights.Delete,
        InheritanceFlags.None,
        PropagationFlags.None,
        AccessControlType.Allow);

    registrySecurity.AddAccessRule(accessRule);

    using var subKey = Registry.CurrentUser.CreateSubKey(CodeMazeRegistryDemoSubKey,
        RegistryKeyPermissionCheck.Default, registrySecurity);

    if (subKey == null)
    {
        return false;
    }

    var isAdded = false;
    var accessControl = subKey.GetAccessControl();
    var accessRules = accessControl.GetAccessRules(true, true, typeof(NTAccount));
    foreach (RegistryAccessRule rule in accessRules)
    {
        if (rule.IdentityReference.Value == user)
        {
            isAdded = true;
            break;
        }
    }
    Registry.CurrentUser.DeleteSubKeyTree(CodeMazeRegistryDemoSubKey);

    return isAdded;
}

No nosso exemplo, criamos a subchave passando os argumentos de verificação de permissão e de segurança do registro. A verificação de permissão tem um valor padrão, o que significa que a verificação de segurança é herdada da chave pai. Além da verificação de permissão, a classe RegistrySecurity nos permite adicionar regras de acesso ao registro. Como exemplo, definimos que o usuário pode ler, gravar e excluir a chave especificando os valores desejados de RegistryRights. RegistryRights é uma enumeração de flags, então os valores se combinam com o operador OR bit a bit (|).

O método GetAccessControl() retorna a definição de segurança do registro para a chave e, por meio dela, podemos enumerar todas as regras definidas. Depois, verificamos se a nossa regra foi aplicada à chave.

Com o método relacionado SetAccessControl(), podemos estabelecer e/ou modificar a definição de segurança da chave (desde que tenhamos o acesso de segurança adequado para alterar direitos de segurança).

Ler e gravar em um Registro do Windows remoto

A classe RegistryKey implementa o método estático OpenRemoteBaseKey(). Com ele, abrimos a chave base em uma máquina com a qual temos uma conexão de rede remota:

public static bool OpenRemoteBaseKey(string machineName)
{
    if (!OperatingSystem.IsWindows())
    {
        return false;
    }

    try
    {
        using var remoteBaseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.CurrentUser, machineName);

        return true;
    }
    catch (ArgumentException)
    {
        // A null machine name surfaces here.
        return false;
    }
    catch (IOException)
    {
        return false;
    }
}

A chave retornada é um identificador aberto como qualquer outro, então a colocamos em uma declaração using. O bloco catch é restrito de propósito: uma máquina inacessível chega como uma IOException, e não como uma exceção de rede ou de segurança, e quem olha de fora erra isso com facilidade.

Para acessar o registro remoto, o serviço Remote Registry do Windows precisa estar habilitado na máquina remota e, claro, precisamos ter direitos de administrador nela.

Conclusão

O Registro do Windows é um local central e estruturado em que as aplicações, o sistema operacional e o hardware armazenam diferentes tipos de valores necessários para o bom funcionamento deles. Por isso, ele representa uma evolução muito bem-vinda dos arquivos .INI e oferece recursos e capacidades muito mais ricos.

O Registro do Windows é específico do sistema operacional Windows, então devemos verificar no código se ele existe e se conseguimos acessá-lo.

As classes Registry e RegistryKey oferecem acesso via código, o que nos permite ler e gravar entradas do Registro do Windows. Elas também oferecem meios para operações mais avançadas, como o controle detalhado dos direitos de acesso e o acesso ao registro em máquinas remotas.

Testado com .NET SDK 10.0.302.