Monthly Archives: March 2012

Reading registry values with CIM

In this post

http://msmvps.com/blogs/richardsiddaway/archive/2012/03/10/migrating-to-cim-doh.aspx

and its predecessors we saw how to enumerate registry sub-keys. But how do we read a registry value?

function get-CIMRegValue{             
[CmdletBinding(DefaultParameterSetName="UseComputer")]             
            
param (             
 [parameter(Mandatory=$true)]            
 [ValidateSet("HKCR", "HKCU", "HKLM", "HKUS", "HKCC")]            
 [string]$hive,            
            
 [parameter(Mandatory=$true)]            
 [string]$key,            
            
 [parameter(Mandatory=$true)]            
 [string]$value,            
            
 [parameter(Mandatory=$true)]            
 [string]            
 [Validateset("DWORD", "EXPANDSZ", "MULTISZ", "QWORD", "SZ")]            
 $type,            
            
  [parameter(ValueFromPipeline=$true,            
   ValueFromPipelineByPropertyName=$true)]            
 [parameter(ParameterSetName="UseComputer")]             
 [string]$computer="$env:COMPUTERNAME",            
             
 [parameter(ValueFromPipeline=$true,            
   ValueFromPipelineByPropertyName=$true)]            
 [parameter(ParameterSetName="UseCIMSession")]             
 [Microsoft.Management.Infrastructure.CimSession]$cimsession            
             
)             
BEGIN{}#begin             
PROCESS{            
            
switch ($hive){            
"HKCR" { [uint32]$hdkey = 2147483648} #HKEY_CLASSES_ROOT            
"HKCU" { [uint32]$hdkey = 2147483649} #HKEY_CURRENT_USER            
"HKLM" { [uint32]$hdkey = 2147483650} #HKEY_LOCAL_MACHINE            
"HKUS" { [uint32]$hdkey = 2147483651} #HKEY_USERS            
"HKCC" { [uint32]$hdkey = 2147483653} #HKEY_CURRENT_CONFIG            
}            
            
switch ($type) {            
"DWORD"     {$methodname = "GetDwordValue"}            
"EXPANDSZ"  {$methodname = "GetExpandedStringValue"}            
"MULTISZ"   {$methodname = "GetMultiStringValue"}            
"QWORD"     {$methodname = "GetQwordValue"}            
"SZ"        {$methodname = "GetStringValue"}            
}            
$arglist = @{hDefKey = $hdkey; sSubKeyName = $key; sValueName = $value}            
            
switch ($psCmdlet.ParameterSetName) {            
 "UseComputer"    {$result = Invoke-CimMethod -Namespace "root\cimv2" -ClassName StdRegProv -MethodName $methodname  -Arguments $arglist -ComputerName $computer}            
 "UseCIMSession"  {$result = Invoke-CimMethod -Namespace "root\cimv2" -ClassName StdRegProv -MethodName $methodname  -Arguments $arglist -CimSession $cimsession }            
 default {Write-Host "Error!!! Should not be here" }            
}            
            
switch ($type) {            
"DWORD"     {$result | select -ExpandProperty uValue}            
"EXPANDSZ"  {$result | select -ExpandProperty sValue}            
"MULTISZ"   {$result | select -ExpandProperty sValue}            
"QWORD"     {$result | select -ExpandProperty uValue}            
"SZ"        {$result | select -ExpandProperty sValue}            
}            
             
}#process             
END{}#end            
            
<# 
.SYNOPSIS
Displays a registry value

.DESCRIPTION
Displays a registry value using WSMAN or DCOM 
to access remote machines 

.PARAMETER  hive
Hive Name. One of "HKCR", "HKCU", "HKLM", "HKUS" or "HKCC"
The name is validated against the set

.PARAMETER  key
The registry key - without the hive name e.g.
"SYSTEM\CurrentControlSet\Services\BITS"

.PARAMETER value
The specific registry value to return for the 
given key

.PARAMETER  type
The type of registry value to return.
Must be one of
"DWORD", "EXPANDSZ", "MULTISZ", "QWORD", "SZ"

.PARAMETER  computer
Name of a remote computer. Connectivity will be by WSMAN.

.PARAMETER  cimsession
An object representing a cimsession. Connectivity is controlled 
by the CIM session and can be WSMAN or DCOM

.EXAMPLE                                                                                       
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value DelayedAutoStart -type DWORD

.EXAMPLE
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value ObjectName -type SZ  

.EXAMPLE
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value DependOnService -type MULTISZ 

.EXAMPLE
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value ImagePath -type EXPANDSZ

.EXAMPLE
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value DelayedAutoStart -type DWORD -computer "."

.EXAMPLE
$cs = New-CimSession -ComputerName Win7test  
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value DelayedAutoStart -type DWORD -cimsession $cs   

.EXAMPLE
$opt = New-CimSessionOption -Protocol Dcom                                                                                                          
$csd = New-CimSession -ComputerName server02 -SessionOption $opt                                                                                    
get-CIMRegValue -hive HKLM -key "SYSTEM\CurrentControlSet\services\BITS" -value DelayedAutoStart -type DWORD -cimsession $csd

.NOTES


.LINK

#>            
            
}


Parameters define the hive, key, value to be read and the type of value.



Registry values come in a number of types:



  • DWORD and QWORD are 32 & 64 bit numbers
  • SZ is a string
  • EXPANDSZ is a string containing environmental variables that gets expanded
  • MULTISZ is a multi-valued string


Parameters to define a computer name or CIM Session are also present



The numeric value for the hive is set in a switch statement. The data type is used to define the method name – each data type has its own method.



The argument list is populated and the method is invoked using a computer name or CIM session as appropriate



The results are decoded according to type.



Full help is provided on the function.

Counting the members of a group

The need for a particular group comes and goes and eventually the group isn’t needed anymore. At that time you have to delete the group but how do you know a group isn’t needed? Probably because its empty and how do you know its empty?

## counts the members of all groups            
##  displays by number of members            
            
"`nMicrosoft"            
$data = @()            
Get-ADGroup -Filter * |             
foreach {            
 $count = (Get-ADGroupMember -Identity $($_.DistinguishedName)).Count            
 if ($count -eq $null){$count = 0}            
 $data += New-Object -TypeName PSObject -Property @{            
   Name = $($_.Name)            
   DistinguishedName = $($_.DistinguishedName)            
   MemberCount = $count            
 }             
}            
$data | sort MemberCount -Descending | Format-Table -AutoSize            
            
"`nAD provider"            
$data = @()            
Get-ChildItem -Filter "(objectclass=group)" -Path Ad:\"DC=Manticore,DC=org" -Recurse |            
foreach {             
  $group = [adsi]"LDAP://$($_.DistinguishedName)"            
                
  $count = ($group.Member).Count            
  if ($count -eq $null){$count = 0}            
  $data += New-Object -TypeName PSObject -Property @{            
    Name = $($group.Name)            
    DistinguishedName = $($group.distinguishedName)            
    MemberCount = $count            
  }            
}            
$data | sort MemberCount -Descending | Format-Table -AutoSize            
            
"`nQuest"            
$data = @()            
Get-QADGroup |             
foreach {            
 $count = (Get-QADGroupMember -Identity $($_.DN)).Count            
 if ($count -eq $null){$count = 0}            
 $data += New-Object -TypeName PSObject -Property @{            
   Name = $($_.Name)            
   DistinguishedName = $($_.DN)            
   MemberCount = $count            
 }             
}            
$data | sort MemberCount -Descending | Format-Table -AutoSize            
            
            
"`nScript"            
$data = @()            
$root = [ADSI]""            
$search = [adsisearcher]$root            
$search.Filter = "(objectclass=group)"            
$search.SizeLimit = 3000            
$search.FindAll() |            
foreach {            
 $group = $_.GetDirectoryEntry()              
             
 $count = ($group.Member).Count            
 if ($count -eq $null){$count = 0}            
 $data += New-Object -TypeName PSObject -Property @{            
   Name = $($group.Name)            
   DistinguishedName = $($group.distinguishedName)            
   MemberCount = $count            
}            
}            
$data | sort MemberCount -Descending | Format-Table -AutoSize            


 



Conceptually all of the solutions are the same – find all the groups in the domain, and count the number of members. The cmdlet solutions are similar as are the script and the provider.



In my testing the script and provider were much faster than the cmdlets

Domain controllers and Global catalogs in the current site

This little script kills two birds with one stone. Which is great unless you happen to be a little bird.

We can use this to discover the domain controllers, and which of them are global catalogs, in the current site

$site = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()            
$site.Servers | select Name, Domain, @{Label="GC";Expression={$_.IsGlobalCatalog()}}


Use the ActiveDirectorySite class’ GetComputerSite method. Pipe the collection of servers and select the name and domain. The IsGlobalCatalog() method on the domain controller object is used to determine if it is a global catalog

Get Global Catalog from DNS

One option for finding global catalog servers is often overlooked – DNS.  In an AD environment DNS stores the SRV records that advertise the services domain controllers can deliver

$dnsserver = "dc02"            
Get-WmiObject -Namespace 'root\MicrosoftDNS' -Class  MicrosoftDNS_SRVType `
-ComputerName $dnsserver -Filter "ContainerName = 'Manticore.org'" |             
Where {$_.OwnerName -like "_gc*"} |            
select TextRepresentation


We are interested in the ‘root\MicrosoftDNS’ name space and the MicrosoftDNS_SRVType records. We want the manticore.org zone and all records where the Ownername is like “_gc*”



The results look like this



_gc._tcp.Site1._sites.Manticore.org IN SRV 0 100 3268 dc02.manticore.org.
_gc._tcp.Site1._sites.Manticore.org IN SRV 0 100 3268 server02.manticore.org.  
_gc._tcp.Manticore.org IN SRV 0 100 3268 dc02.manticore.org.  
_gc._tcp.Manticore.org IN SRV 0 100 3268 server02.manticore.org. 

Disable Global catalog

Of course once you’ve learned how to enable something you need to know how to disable

$dc = "dc02.manticore.org"            
$contextType = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::DirectoryServer            
$context = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList $contextType, $dc            
$gc = [System.DirectoryServices.ActiveDirectory.GlobalCatalog]::GetGlobalCatalog($context)            
$gc.DisableGlobalCatalog()


Similar to enabling but this time we create a GlobalCatalog object and use the DisableGlobalCatalog() method

Enable Global catalog

Enabling a domain controller is simple through the GUI however its just even easier with PowerShell

$dc = "dc02.manticore.org"            
$contextType = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::DirectoryServer            
$context = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList $contextType, $dc            
$gc = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($context)            
$gc.EnableGlobalCatalog()


Create a context type of Directory Server. Create a context of that type for the domain controller to be promoted. Get the domain controller as a System.DirectoryServices.ActiveDirectory.DomainController object and call the EnableGlobalCatalog() method.



There is an alternative using ADSI but I think this is simpler. Put it in a function with the domain controller name as a parameter and its good to go.

Finding Global Catalog servers in the forest

Global catalogs are domain controllers that also hold a subset of information on all objects in the forest. They are a required infrastructure component for Universal Groups and Exchange among other things.

            
"`nMicrosoft"            
Get-ADForest |             
select -ExpandProperty GlobalCatalogs |            
Format-Table            
            
"`nScript"            
$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()            
$for.FindAllGlobalCatalogs() |             
select Name, IPAddress |             
Format-table


Only two options for discovering them. The Microsoft cmdlets provide a Get-ADForest that returns an object representing the forest – it includes a collection of global catalog names



The Script uses System.DirectoryServices.ActiveDirectory.Forest and the GetCurrentForest() method to discover the forest object and the collection of global catalogs.



There doesn’t seem to be an easy way to do the job through the provider or the Quest cmdlets

Move a single FSMO role

Sometimes you just want to move a single FSMO role

 

function move-afsmo {            
[CmdletBinding()]            
param([string]$server,             
            
[ValidateSet("schema", "domain", "rid", "infra", "pdc")]            
[string]$fsmo            
)            
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()            
$sid = ($dom.GetDirectoryEntry()).objectSid            
$dc = [ADSI]"LDAP://$server/rootDSE"            
            
switch ($fsmo.ToLower()){            
    "schema" {$role = "becomeSchemaMaster"; break}            
    "domain" {$role = "becomeDomainMaster"; break}            
    "rid"    {$role = "becomeRidMaster"; break}            
    "infra"  {$role = "becomeInfraStructureMaster"; break}            
    "pdc"    {$role = "becomePDC"; break}            
}            
            
if ($role -eq "becomePDC"){ $dc.Put($role, $sid[0])}            
else {$dc.Put($role, 1) }            
$dc.SetInfo()            
}


 



This function takes a domain controller name and a role and performs the transfer.



move-afsmo -server dc02 -fsmo schema                                       
move-afsmo -server dc02 -fsmo domain                                       
move-afsmo -server dc02 -fsmo rid                                          
move-afsmo -server dc02 -fsmo infra                                        
move-afsmo -server dc02 -fsmo pdc 



The roles are validated on input to determine the given value is in the set of roles. A switch statement sets the role to input to the Put() method. The transfer is performed as previously

Move all fsmo roles

When AD is first installed all of the FSMO roles are placed on the first domain controller in the forest. If you want to move all of the FSMO roles

$server = "server02"            
            
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()            
$sid = ($dom.GetDirectoryEntry()).objectSid            
$dc = [ADSI]"LDAP://$server/rootDSE"            
            
$fsmo = "becomeSchemaMaster", "becomeDomainMaster", "becomeRidMaster", "becomeInfraStructureMaster", "becomePDC"            
foreach ($role in $fsmo){             
    if ($role -eq "becomePDC"){ $dc.Put($role, $sid[0])}            
    else {$dc.Put($role, 1) }            
    $dc.SetInfo()            
}


Select the server that you want to move them to. Get the domain SID (needed for moving the PDC emulator role) and a directory entry for the target DC.



Loop through the fsmo roles and trigger the change.



There isn’t an easy way to do this with the provider or the cmdlets.



There is a method on the System.DirectoryServices.ActiveDirectory.DomainController class called TransferRoleOwnership. It doesn’t fully work with Windows 2008 and above and I would recommend against its use

Up coming User group sessions

The sessions for the next few months are:

  • 27 March – PowerShell v3 CIM cmdlets and “cmlets over objects”
  • April – Managing Windows Server 8 with PowerShell
  • May – Managing Windows Server 8 with PowerShell

No thats not a mistake – there is so much new PowerShell functionality in Windows server 8 that two sessions will just scratch the surface.  I’m delivering the April session and PowerShell MVP Jonathan Medd is delivering the May session.

Details on March’s sessions from

http://msmvps.com/blogs/richardsiddaway/archive/2012/03/06/uk-powershell-group-march-2012.aspx

As always the session will be recorded and made available afterwards.