IN THIS CHAPTER
Managing Windows services
Managing processes
Reading and modifying the registry
Modifying network settings
Retrieving performance counters
Setting regional settings
Maintaining local accounts
Configuring remote DCOM
This chapter covers a lot of ground because advanced server management is a complex subject. Microsoft has provided a hodgepodge of cmdlets in Windows PowerShell that can help with the various server management tasks. However, quite a few of these cmdlets are not designed to work against remote computers. In multiple cases, cmdlets within the same functional area will have different behaviors. For instance, the Set-Service cmdlet accepts a ComputerName parameter, whereas the rest of the *-Service cmdlets that modify services do not.
This chapter covers two options for managing remote servers. You can use remoting cmdlets such as the Invoke-Command cmdlet, or you can use WMI with a combination of methods. This chapter focuses on using WMI when a cmdlet does not accept the ComputerName parameter.
You manage services with the Get-Service, Stop-Service, Start-Service, Suspend-Service, Resume-Service, Restart-Service, and Set-Service cmdlets. Of these, the Get-Service and Set-Service cmdlets accept the ComputerName parameter. The remaining cmdlets require the remote server to be configured for remoting. As an alternative to remoting, you can manage services with the Get-WmiObject cmdlet.
You can list services that are running on remote servers with the Get-Service cmdlet, passing the optional parameter ComputerName. Comparing running services can help when you are troubleshooting issues. The Get-Service cmdlet returns all services, so you need to provide a filter to return only running services. You do this with the Where-Object cmdlet. Finally, you will need to pass the output to the Select-Object cmdlet, one of the Format-* cmdlets, or one of the Export-* cmdlets to view the computer name. The following example displays all running services on the servers ExchCAS01, ExchCAS02, ExchCAS03, and ExchCAS04. The output shows the server name, the service name, and the service display name.
$Computers = “ExchCAS01”,“ExchCAS02”,“ExchCAS03”,“ExchCAS04” $Filter = @{ FilterScript = {$_.Status -eq “Running”} } $Select = @{ Property = “MachineName”,“Name”,“DisplayName” } foreach ($Computer in $Computers) { Get-Service -ComputerName $Computer | Where-Object @Filter | Select-Object @Select }
MachineName Name DisplayName ----------- ---- ----------- ExchCAS01 AppHostSvc Application Host Helper Service ExchCAS01 Appinfo Application Information ExchCAS01 AudioEndpointBuilder Windows Audio Endpoint Builder ExchCAS01 AudioSrv Windows Audio ExchCAS01 BFE Base Filtering Engine ExchCAS01 BITS Background Intelligent Transfer Service ExchCAS01 CertPropSvc Certificate Propagation ...
The Get-Service cmdlet accepts the optional parameter Name, which enables you to retrieve only specific services. This parameter accepts wildcard input as well as an array of names. The following example returns a list of servers that have the Exchange Information Store and Exchange System Attendant services running:
$Computers = “ExchCAS01”,“ExchCAS02” $Service = “MSExchangeIS”,“MSExchangeSA” $Filter = @{ FilterScript = {$_.Status -eq “Running”} } $Select = @{
Property = “MachineName”,“Name”,“DisplayName” } $ServiceHash = @{ Name = $Service ErrorAction = “SilentlyContinue” } foreach ($Computer in $Computers) { Get-Service @ServiceHash -ComputerName $Computer | Where-Object @Filter | Select-Object @Select }
MachineName Name DisplayName ----------- ---- ----------- ExchCASOl MSExchangelS Microsoft Exchange Information Store ExchCASOl MSExchangeSA Microsoft Exchange System Attendant ExchCAS02 MSExchangelS Microsoft Exchange Information Store ExchCAS02 MSExchangeSA Microsoft Exchange System Attendant
On many occasions, services may be set to start automatically, but fail to start. Unfortunately, the Get-Service cmdlet does not return information on the service start type. For this information, you need to use the Get-WmiObject cmdlet. The class you call is the Win32_Service class.
This class returns the service State and StartMode, among other properties. Those properties can be passed to the Filter parameter of the Get-WmiObject cmdlet to limit results to just services that are set to start automatically and are not running. The following example returns a list of services that are set to start automatically on the FileServerOl and FileServer02 servers and are not running:
$Computers = “FileServer01”,“FileServer02” $WmiObject = @{ Class = “Win32_Service” Filter = “StartMode=‘Auto’ and State!=‘Running’” } foreach ($Computer in $Computers) { $Select = @{ Property = “SystemName”,“Name” } Get-WmiObject @WmiObject -ComputerName $Computer | Select-Object @Select }
The previous example shows which non-running services are set to run automatically. You can start services on remote servers with the StartService() method of the Win32_Service class. The following example extends the previous example to attempt to start all stopped services:
$Computers = “FileServer01”,“FileServer02” $WmiObject = @{ Class = “Win32_Service” Filter = “StartMode=‘Auto’ and State!=‘Running’” } foreach ($Computer in $Computers) { foreach ($Svc in Get-WmiObject @WmiObject -ComputerName $Computer) { Write-Host “Starting the” $Svc.DisplayName “service on $Computer” $Svc.StartService() | Out-Null } }
Another method of the Win32_Service class is the ChangeStartMode() method. This method enables you to set a service to disabled, which will prevent it from starting. Suppose that you previously discovered that the service Windows Audio was running on one of your servers. Unneeded services provide a potential security problem, so you would probably want to disable and stop the Windows Audio service on that server. The following example accomplishes this task:
$ServiceObject = @{ Class = “Win32_Service” Filter = “Name = ‘AudioSrv’” ComputerName = “DC01” } $Service = Get-WmiObject @ServiceObject $Service.ChangeStartMode(“Disabled”) $Service.StopService()
For more examples, which include waiting for the service to start as well as reporting on failures, see the book's website.
Think of Windows processes as programs or specific parts of an program. For instance, an antivirus program might use several processes. Each processor on a server can run one process at a time. As the process is running, every other process is waiting for processor time.
A process that is not responding will, at best, stop a program from responding, and at worst, stop the entire server from responding. In this section, you learn how to discover and stop those processes.
Processes are managed with the Get-Process, Stop-Process, Wait-Process, Debug-Process, and Start-Process cmdlets. With the exception of Get-Process, these cmdlets manage processes on the local computer.
The Get-Process cmdlet supports the ComputerName parameter, so it does not require that remoting be enabled on the remote server. The other process cmdlets require that remoting is enabled on remote servers. As an alternative to enabling remoting, you can stop processes on remote servers with WMI. Both methods are covered in the section “Stopping Processes on Remote Servers.”
The Get-Process cmdlet, when run without parameters, lists all processes on the local computer. To view processes on remote servers, you pass the server names to the ComputerName parameter. The default view that the Get-Process cmdlet returns does not include the computer name, so you will have to use the Select-Object cmdlets or one of the Format-* cmdlets to view the machine name onscreen, or pass the output through one of the Export-* cmdlets to save the data to disk. The following example returns all processes on the servers FileServer01 and FileServer02:
$Process = @{ ComputerName = “FileServer01”,“FileServer02” } $Sort = @{ Property = “MachineName”,“ProcessName” } $Table = @{ Property = “MachineName”,“ProcessName”,“Id”,“NPM”,“PM”,“WS”,“VM” AutoSize = $True } Get-Process @Process | Sort-Object @Sort | Format-Table @Table
Perhaps a more interesting exercise would be to list processes on remote servers that are not responding. One of the properties that the Get-Process cmdlet returns is the Responding property. The following example returns which processes are not responding on the server Server01:
$Computer = “Server01” $Process = @{ ComputerName = $Computer } $Filter = @{ FilterScript = {$_.Responding -ne $True} } Get-Process @Process | Where-Object @Filter
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------ ----- ---- ---- ---- ------ -- ----------- 965 13 2788 4940 45 496 csrss 192 13 19264 13400 58 568 csrss 404 34 20612 24344 342 1760 dfsrs 164 15 4440 8060 39 2068 dfssvc 5220 22171 314340 312252 349 1816 dns 444 29 48252 50488 154 4948 Dropbox
Suppose you wanted to stop the Dropbox process from the previous example. If the process were running on the local computer, you could stop the process with the Stop-Process cmdlet, passing the process ID to the Id parameter as Stop-Process -Id 4948.
The Stop-Process cmdlet does not accept the ComputerName parameter, so you will need to use remoting to run the command on the remote server or use the Get-WmiObject cmdlet, which does accept the ComputerName parameter.
The following example stops the Dropbox process on the server Server01 using the Invoke-Command cmdlet. This will only succeed on servers that have remoting enabled.
Invoke-Command -ComputerName Server01 -ScriptBlock {Stop-Process -Id 4948}
The Get-WmiObject cmdlet will work on any computer on which you have permission to run WMI queries. You call the InvokeMethod() method of the Win32_Process class to stop the process. The following example is the functional equivalent of using the Invoke-Command cmdlet in the previous example, rewritten to avoid the requirement for remoting:
$ProcessSplat = @{ Class = “Win32_Process” Filter = “ProcessId = 4948” ComputerName = “Server01” } (Get-WmiObject @ProcessSplat).InvokeMethod(“Terminate”, $null)
Certain processes will not allow you to stop them because they are required for Windows to function. In those cases, you would need to restart the server.
You can also stop a process by name. I recommend using the process ID because each process has a unique ID, whereas you could have several processes with the same name. The examples shown in this section would fail if there were more than one process to stop.
Windows PowerShell includes a provider that enables you to read and write to the two most common registry hives on the local computer. With this provider, you can access the HKEY_Local_Machine and HKEY_Current_User registry hives as if they were a file system. Registry hives are a logical collection of keys, subkeys, and values within the registry.
You can also create your own provider to access the other registry hives. This is accomplished with the New-PSDrive cmdlet, passing the parameters Name, PSProvider, and Root. The following example creates the local provider named HKCR pointing to the Registry provider in the root HKEY_CLASSES_ROOT:
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
You can access the registry on a remote computer with the .NET classes Microsoft.Win32.RegistryHive and Microsoft.Win32.RegistryKey. If you manage remote registries on an ongoing basis, you may want to create custom type accelerators for these classes. Type accelerators are a shortcut to an underlying .NET type name. The type accelerator [string] points to the .NET type System.String. Using a type accelerator allows you to reference the underlying type without typing the type name, or even necessarily knowing the name. The example in Listing 9-1 creates these type accelerators. You can either run the code in Listing 9-1 each time you work with remote registry, or put the code in your $Profile script so that it is available every time you load Windows PowerShell. The examples in this chapter use these type accelerators.
LISTING 9-1 Creating Type Accelerators for Registry Access
$accelerators = [type]::gettype(“System.Management.Automation.TypeAccelerators” $acceleratorRegHive = [type]::gettype(“Microsoft.Win32.RegistryHive”) $acceleratorRegKey = [type]::gettype(“Microsoft.Win32.RegistryKey”) $accelerators::Add(“reghive”, $acceleratorRegHive) $accelerators::Add(“regkey”, $acceleratorRegKey)
You could also read remote registry values using the Invoke-Command cmdlet, if the remote servers have remoting enabled.
As mentioned, you can read a local registry key by accessing the registry provider directly. The two included providers (HKLM and HKCU) can be accessed with the Set-Location cmdlet, passing the parameter Path. Once you have set the location to the registry key of your choice, you retrieve a value with the Get-ItemProperty cmdlet, passing the Path parameter.
The following example returns which version of Windows PowerShell is installed on the local computer:
Set-Location -Path HKLM:SOFTWAREMicrosoftPowerShell1PowerShellEngine (Get-ItemProperty -Path .).PowerShellVersion
You could also gather the data without changing location to the registry path, by passing that information to the Path parameter of the Get-ItemProperty cmdlet. The following example shows this. The example uses the $Path variable to hold the name of the registry key, and passes the key to the Path parameter of the Get-ItemProperty cmdlet:
$Path = “HKLM:SOFTWAREMicrosoftPowerShell1PowerShellEngine” (Get-ItemProperty -Path $Path).PowerShellVersion
As previously mentioned, you read a registry value remotely with the .NET classes Microsoft.Win32.RegistryHive and Microsoft.Win32.RegistryKey. To read the value of a registry key, you first have to create a Microsoft.Win32.RegistryHive value pointing to the hive you are interested in. This can be ClassesRoot, CurrentUser, LocalMachine, Users, or PerformanceData.
Once you have your hive object, you open the remote hive with the OpenRemoteBaseKey() method of the Microsoft.Win32.RegistryKey class. This method takes the hive and computer name as parameters. Once you have the remote hive open, you open the subkey and read the value with the OpenSubKey() and GetValue() methods of the Microsoft .Win32.RegistryKey class, respectively.
The following example extends the previous example to show which version of Windows PowerShell is installed on the servers FileServer01 and FileServer02. This example uses the custom type accelerators created in Listing 9-1. If you have not loaded them, you will need to do that before running the example.
foreach ($Server in “FileServer01”,“FileServer02”) { $Version = $null $Message = $null $keyName = “SOFTWAREMicrosoftPowerShell1PowerShellEngine” $valueName = “PowerShellVersion” $regHive = [reghive]“LocalMachine” try
{ $regKey = [regkey]::OpenRemoteBaseKey($regHive,$Server) } catch { $Message = “$Server cannot be contacted. Is it online?” } if ($Message -eq $null) { try { $Version = ($regKey.OpenSubKey($keyName)).GetValue($ValueName) $Message = “$Server has version $Version of Windows PowerShell” } catch { $Message = “$Server does not seem to have Windows PowerShell” } } Write-Output $Message }
FileServer01 has version 2.0 of Windows PowerShell FileServer02 does not seem to have Windows PowerShell
Setting registry values is more complex than reading them, because you need to specify the type of value you are setting. Possible value types are listed in Table 9-1.
Besides setting values for existing registry keys, Windows PowerShell provides methods to create new registry keys.
Creating a new key on the local computer can be accomplished with the New-Item cmdlet, passing the Path and ItemType parameters. Registry keys are treated as directories by the built-in registry providers. Thus, the item type value is Directory. The following example creates the new key PowerShellBible in the Software key of the HKEY_Local_Machine hive:
New-Item -ItemType Directory -Path “HKLM:SoftwarePowerShellBible”
Note
You may need to run Windows PowerShell in an elevated session to create a new registry key.
The new key contains an empty default value. If you want to create a new key with subkeys, you need to create it from the top level down. The following example creates the registry keys as shown under the previously created PowerShellBible key. If you attempted to create the second key first, the command would fail.
New-Item -ItemType Directory -Path “HKLM:SoftwarePowerShellBibleFirst” New-Item -ItemType Directory -Path “HKLM:SoftwarePowerShellBibleFirstSecond”
Creating a registry value locally is accomplished with the Set-ItemProperty cmdlet, passing the Path, Name, Type, and Value parameters. The following example creates the new values as shown:
$Path = “HKLM:SoftwarePowerShellBible” Set-ItemProperty -Path $Path -Name “Example1” -Value 123 -Type Dword Set-ItemProperty -Path $Path -Name “Example2” -Value “Test” -Type String
You modify an existing value in the same manner as creating a new value. Suppose you realized that the Example1 value was supposed to be a string value. The following example changes the Dword value 123 to a String value of q123:
$Path = “HKLM:SoftwarePowerShellBible” Set-ItemProperty -Path $Path -Name “Examplei” -Value “q123” -Type String
Creating a new key on a remote computer can be accomplished with the .NET classes Microsoft.Win32.RegistryHive and Microsoft.Win32.RegistryKey. Examples shown use the custom type accelerators shown in Listing 9-1.
Once again, if remoting is enabled on the remote computers, you can create registry keys and values with the Invoke-Command cmdlet. The Invoke-Command cmdlet has the benefit of accepting credentials, which allows you to run Windows PowerShell as a nonprivileged user and invoke commands as an administrator.
The first step in creating a new key or value with the .NET classes is opening the parent key in read-write mode. You do this with the OpenSubKey() method of the Microsoft.Win32.RegistryKey class. This method has an overload that accepts a Boolean value as its second parameter. When this value is set to $True, the key is opened in read-write mode.
Note
An overload allows a programmer to have multiple methods with the same name that accept varying types or quantities of arguments. In this case, you can call the OpenSubKey() method with one, two, or three parameters. The first parameter is a string, and the second can be a Boolean as we used, or a RegistryKeyPermissionCheck object. The third parameter would be a RegistryRights object.
Creating a new key is accomplished with the CreateSubKey() method of the Microsoft.Win32.RegistryKey class, and creating a value is accomplished with the SetValue() method of the Microsoft.Win32.RegistryKey class. If not specified, the SetValue() method attempts to infer the value type.
The following example creates the new key PowerShellBible in the Software key of the HKEY_Local_Machine hive, and adds the values named Example1 and Example2 on both servers listed:
foreach ($Server in “FileServer01”,“FileServer02”) { $keyName = “SOFTWARE” $newKeyName = “PowerShellBible” $value1Name = “Example1” $value2Name = “Example2” $value1 = 123 $value2 = “Test” $value1Type = “Dword” $value2Type = “String” $regHive = [reghive]“LocalMachine” $regKey = [regkey]::OpenRemoteBaseKey($regHive,$Server) $key = $regKey.OpenSubKey($keyName,$True) $key.CreateSubKey($newKeyName) $key = $regKey.OpenSubKey(“$keyName$newKeyName”,$True) $key.SetValue($value1Name, $value1, $value1Type) $key.SetValue($value2Name, $value2, $value2Type) }
Network configuration on remote servers can be retrieved with the Win32_NetworkAdapterConfiguration class of the Get-WmiObject cmdlet. By default, this class returns information on all network adapters on the computer. You can filter the returned data to only include adapters where IP is enabled to cut down on the extra data. The following example retrieves information on each enabled adapter on the server Exch2010:
$WmiObject = @{ Class = “Win32_NetworkAdapterConfiguration” ComputerName = “Exch2010” Filter = “IPEnabled = ‘true’” #The filter acts on the string ‘true’, not the #Boolean $True. } Get-WmiObject @WmiObject
DHCPEnabled : False IPAddress : {192.168.1.10, fe80::acee:78b3:604e:5b} DefaultIPGateway : {192.168.1.1} DNSDomain : ServiceName : VMSMP Description : External Index : 16
As you can see, the information returned in the default view is rather sparse. Piping the output through the Format-List cmdlet, passing the Property parameter with the value of * returns all properties of each network adapter. On the network adapter on my server, this is 71 properties. Some of the properties, like the DNSDomain above, will be empty.
DNS settings are stored in the properties of the Win32_NetworkAdapterConfiguration class. The following example shows the DNS settings for the server Exch2010:
$WmiObject = @{ Class = “Win32_NetworkAdapterConfiguration” ComputerName = “Exch2010” Filter = “IPEnabled = ‘true’” } Get-WmiObject @WmiObject | Format-List -Property dns*
You can build on the previous example to gather DNS settings for a group of servers in a foreach loop. Because there may be multiple network adapters in each server, the network adapters are also handled in a foreach loop. Finally, the DNSDomainSuffixSearchOrder and DNSServerSearchOrder properties are arrays that may have multiple values, so you cast those to a string type, and replace spaces with a semicolon and a space to make them more readable. This also allows those properties to be exported to a .csv file. The following example returns a list of the DNS settings for the servers Exch2010, fileServer01, and PrintServer23:
$Servers = “Exch2010”,“fileServer01”,“PrintServer23” $ServerDNS = @()
foreach ($Server in $Servers) { $WmiObject = @{ Class = “Win32_NetworkAdapterConfiguration” ComputerName = $Server Filter = “IPEnabled = ‘true’” } $DnsSettings = @(Get-WmiObject @WmiObject) foreach ($DnsSetting in $DnsSettings) { $Dns = “” | Select-Object -Property DNSHostName, DNSDomain, DNSDomainSuffixSearchOrder, DNSEnabledForWINSResolution, DNSServerSearchOrder, DomainDNSRegistrationEnabled, FullDNSRegistrationEnabled $TempSuffixSearch = [string]$DnsSetting.DNSDomainSuffixSearchOrder $TempServerSearch = [string]$DnsSetting.DNSServerSearchOrder $Dns.DNSHostName = $DnsSetting.DNSHostName $Dns.DNSDomain = $DnsSetting.DNSDomain $Dns.DNSDomainSuffixSearchOrder = $TempSuffixSearch.Replace(“ ”,“; ”) $Dns.DNSEnabledForWINSResolution = $DnsSetting.DNSEnabledForWINSResolution $Dns.DNSServerSearchOrder = $TempServerSearch.Replace(“ ”,“; ”) $Dns.DomainDNSRegistrationEnabled = $DnsSetting.DomainDNSRegistrationEnabled $Dns.FullDNSRegistrationEnabled = $DnsSetting.FullDNSRegistrationEnabled $ServerDNS += $Dns } } $ServerDNS
This example could easily be extended to save the results to a file or to gather DNS settings for more servers.
Changing the network configuration on remote servers can be accomplished with a combination of the Get-WmiObject and Invoke-WmiMethod cmdlets.
Caution
Care should be taken because you can easily cause a server to lose connection to the network by passing incorrect parameters, and the server may momentarily drop the network connection while changes take effect.
Modifying the DNS suffix search order is accomplished with the Invoke-WmiMethod cmdlet, passing the ComputerName, Class, Name, and ArgumentList parameters. The class used is the Win32_NetworkAdapterConfiguration class. The ArgumentList parameter requires an array of objects for the first value, and a $null for the second value. The method invoked is the SetDNSSuffixSearchOrder() method.
Note
The methods of the Win32_NetworkAdapterConfiguration class are documented at http://msdn.microsoft.com/en-us/library/aa394217(v=VS.85).aspx.
The following example sets the DNS suffix search order to contoso.com, contoso.co.us, and the previous DNS suffix search order, in that order:
$WmiObject = @{ ComputerName = “Exch2010” Class = “Win32_NetworkAdapterConfiguration” } $Nics = @(Get-WmiObject @WmiObject -Filter “IPEnabled = ‘true’”) foreach ($Nic in $Nics) { $OldSuffix = $Nic.DNSDomainSuffixSearchOrder $Suffix = “contoso.com”, “contoso.co.us” + $OldSuffix $InvokeObject = @{ Name = “SetDNSSuffixSearchOrder” ArgumentList = @($Suffix), $null } Invoke-WmiMethod @WmiObject @InvokeObject }
Modifying an IP address can be accomplished by creating an object reference to the network interface card with the Win32_NetworkAdapterConfiguration class of the Get-WmiObject cmdlet, and calling the EnableStatic() method of that object. The following example modifies the third octet of each IPv4 address to a 0. The third octet of each DNS server IP address will also be changed to a 0.
$WmiObject = @{ ComputerName = “Exch2010” Class = “Win32_NetworkAdapterConfiguration” } $ThirdOctet = 0 $NewDns = @() $Nics = @(Get-WmiObject @WmiObject -Filter “IPEnabled = ‘true’”) foreach ($Nic in $Nics) { [ipaddress]$OldIP = $($Nic.IPAddress -match “^d.d.d.d”) $NewIp = $OldIP.GetAddressBytes()[0..1] -join “.” # the -join operator concatenates strings in the order # in which they appear. The “.” causes them to be # delimited by a dot as an IP address would be. $NewIp = $NewIp, $ThirdOctet,$OldIP.GetAddressBytes()[3] -join “.” $Subnet = $Nic.IPSubnet[0].ToString() $OldDNS = @($nic.DNSServerSearchOrder) foreach ($Dns in $OldDNS)
{ [ipaddress]$InDns = $Dns $OutDns = $InDns.GetAddressBytes()[0..1] -join “.” $OutDns = $OutDns, $ThirdOctet,$InDns.GetAddressBytes()[3] -join “.” $NewDns += $OutDns } $Nic.SetDNSServerSearchOrder($NewDns) $Nic.EnableStatic($NewIp,$Subnet) }
Microsoft Windows operating systems and applications provide performance counters designed to provide information on the health or usage of the application or operating system. Hundreds of performance counters are available on any given system. Windows PowerShell includes the Get-Counter cmdlet, which is designed to retrieve performance counter data from the local and remote computers. Because so many counters are available on any given computer, the Get-Counter cmdlet includes the ListSet parameter, which allows you to determine counters that may be of importance in a given situation. The following example shows which counter sets that target the processor are available on the local computer:
Get-Counter -ListSet “Processor*” | Select-Object -Property CounterSetName
CounterSetName -------------- Processor Information Processor Processor Performance
Now that you know that the local computer includes the counter set Processor, you can see which counters that set includes by once again calling the Get-Counter cmdlet. This time, you target the specific set you are interested in and pipe the output through the Select-Object cmdlet to list just the counters.
Get-Counter -ListSet “Processor” | Select-Object -Expand Counter
Processor(*)\% Processor Time Processor(*)\% User Time Processor(*)\% Privileged Time Processor(*)Interrupts/sec …
Finally, you can start gathering data from a counter. In this case, you will be gathering data from the Processor(*)\% User Time counter. Once again, this is accomplished with the Get-Counter cmdlet, passing the Counter parameter. When run with just the Counter parameter, the Get-Counter cmdlet returns only one set of data. Further parameters enable you to set the SampleInterval and MaxSamples or to specify that the cmdlet gathers data continuously using the Continuous switch parameter. The following example gathers data from the local computer's Processor(*)\% User Time counter every 3 seconds for 10 samples:
$Counter = @{ Counter = “Processor(*)\% User Time” SampleInterval = 3 MaxSamples = 10 } Get-Counter @Counter
The parameter ComputerName enables you to gather data from remote computers. The following example modifies the previous example to retrieve the Processor(*)\% User Time counter every 3 seconds for 10 samples from the server Exch2010:
$Counter = @{ Counter = “Processor(*)\% User Time” SampleInterval = 3 MaxSamples = 10 ComputerName = “Exch2010” } Get-Counter @Counter
You can use the ListSet parameter along with the ComputerName parameter to see which counters are available on a remote computer.
Regional settings affect how the server processes numbers, dates, currency, keyboard input, and so on. Windows operating systems include predefined settings for most countries. As you can imagine, regional settings can be very complex. The regional settings are stored in the registry in the HKEY_Current_User hive, under the Control Panel key in the International subkey. Perhaps the simplest method of modifying regional settings on remote computers is copying valid settings from one computer to another. This can be easily accomplished with WMI. The following example copies the regional settings from WinDC01 to WinDC02 and WinDC03:
$hive =“CurrentUser” $keyName = “Control PanelInternational” $Computers = “WinDC02”, “WinDC03” $Source = “WinDC01” $Hive = [Microsoft.Win32.RegistryHive]$hive
$SourceKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive,$Source) $SourceSubkey = $SourceKey.OpenSubKey($keyName) $valueNames = $SourceSubkey.GetValueNames() Foreach ($Computer in $Computers) { $regHive = [Microsoft.Win32.RegistryHive]$hive $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($regHive,$Computer) $Subkey = $regKey.OpenSubKey($keyName,$True) foreach ($valueName in $valueNames) { $SourceValue = $SourceSubkey.GetValue($valueName) $Subkey.SetValue($valueName,$SourceValue) } }
Local accounts can be managed with the DirectoryEntry class of the System.DirectoryServices namespace. Windows PowerShell includes the [adsi] type accelerator for this class.
Modifying local users and groups can be accomplished by creating an object pointing to the user or group using the [adsi] type accelerator. Modifications to user accounts are saved to the computer by calling the SetInfo() method of the user object. Modifications to groups are written to the computer immediately.
Note
When you use a type accelerator, you enclose it in square brakets.
Once you have created a group object, you add members to the group by calling the Add() method of the object. Group members can be either a local or domain user. A user can be removed from a local group with the Remove() method of the group object. The following example adds the domain user Contosokarlm to the Backup Operators group on the server FileServer01:
$Computer = “FileServer01” $Member = “karlm” $Domain = “Contoso” $GroupName = “Backup Operators” ([ADSI]“WinNT://$Computer/$GroupName,group”).Add(“WinNT://$Domain/$Member”)
Modifying the final line to remove the domain reference adds a local user to the group. This is shown in the following example:
$Computer = “FileServer01” $Member = “Operator” $GroupName = “Backup Operators” ([ADSI]“WinNT://$Computer/$GroupName,group”).Add(“WinNT://$Member”)
You can also add a domain group to a local group by replacing the user's name with the group name in the $Member variable.
Removing users from local groups requires exactly the same syntax as adding users. The only difference is that the Remove() method is called. The following example removes the user contosoartb from the local group Power Users on the server Exch2010:
$Computer = “Exch2010” $Member = “bartb” $Domain = “Contoso” $GroupName = “Power Users” ([ADSI]“WinNT://$Computer/$GroupName,group”).Remove(“WinNT://$Domain/$Member”)
You modify a user account much the same as you modify a group. As a security precaution, many organizations rename the built-in administrator account to attempt to prevent unauthorized access. The following example renames the Administrator account on the server FileServer01 to ServerAdmin, sets the description of the account to Local Administrative User, and sets the password to never expire. This final step is accomplished by modifying the UserFlags property of the user object. The UserFlags property is modified by using the inclusive bitwise OR operator -bor. Notice that the Rename() method must be called before the other methods.
$Computer = “FileServer01” $UserName = “Administrator” $DONT_EXPIRE_PASSWD = 0x10000 #Use the symbolic constant “DONT_EXPIRE_PASSWD” as it is #easier to see what we are doing than the hexadecimal version $User = ([ADSI]“WinNT://$Computer/$UserName”) $User.Rename(“ServerAdmin”) $User.Description = “Local Administrative User” $User.UserFlags = $User.UserFlags.Value -bor $DONT_EXPIRE_PASSWD $User.SetInfo()
Note
For more information on the various user flags, see http://msdn.microsoft.com/en-us/library/aa772300%28v=VS.85%29.aspx.
Creating and deleting local users and groups can be accomplished with the [adsi] type accelerator in much the same manner as modifying existing accounts. The methods used are the Create() and Remove() methods. The Create() method requires that the SetInfo() method be called directly afterward, because the Create() method creates the object only in memory.
The following example creates the new group WMI Users on the computer Exch2010, and sets the description to WMI Users for the server. Notice the seemingly redundant use of the Setinfo() method. This is required because the object does not exist on the computer until after the initial SetInfo() call.
$Computer = “Exch2010” $Group = ([ADSI]“WinNT://$Computer”).Create(“Group”, “WMI Users”) $Group.SetInfo() $Group.Description = “WMI Users for the server” $Group.SetInfo()
The following example creates the new user wmiaccount on the server Exch2010 and sets the password, description, and full name as indicated:
$Computer = “Exch2010” $User = ([ADSI]“WinNT://$Computer”).Create(“User”, “wmiaccount”) $User.SetPassword(“P@ssw0rdZero”) $User.SetInfo() $User.Description = “WMI User for the server” $User.FullName = “WMI User” $User.SetInfo()
Local users and groups are considered children of the computer, so when removing these accounts, you reference the Children property of the computer. Unlike the Create() method, the Remove() method removes the object from the computer directly; there is no need to call the SetInfo() method. The following two examples remove the local user wmiaccount and group WMI Users from the computer Exch2010:
$Computer = “Exch2010” $User = “wmiaccount” ([ADSI]“WinNT://$Computer,computer”).Children.Remove(“WinNT://$Computer/$User”) $Computer = “Exch2010” $Group = “Wmi Users” ([ADSI]“WinNT://$Computer,computer”).Children.Remove(“WinNT://$Computer/$Group”)
The Distributed Component Object Model (DCOM) allows communication between objects on different computers on a LAN or WAN, or over the Internet. Accessing WMI on remote computers requires that you have the proper permissions to use DCOM and WMI on the remote computer.
You can view DCOM permissions on a local computer or on a remote computer by querying a registry key. The key is in the HKEY_Local_Machine hive, in the path SoftwareMicrosoftOle, and is a binary value known as MachineLaunchRestriction. Because the value is a binary value, you cannot merely read the value and make sense of it. Locally, the cmdlet Get-ItemProperty retrieves the data; however, you will need to convert it to a Win32 security descriptor using the BinarySDToWin32SD() method of the Win32_SecurityDescriptorHelper class, which is part of the System.Management.ManagementClass class. The following example returns the binary data in the MachineLaunchRestriction value on the local machine. As you can see from the small sample shown, the data returned is a seemingly meaningless bunch of numbers.
(Get-ItemProperty -Path HKLM:SOFTWAREMicrosoftOle).MachineLaunchRestriction
1 0 4 128 120 …
Because viewing the DCOM permissions is accomplished by reading the registry, and reading a registry remotely can be accomplished with the Get-WmiObject cmdlet, I will use this method in the following examples, which will work locally or remotely. The following simple example expands on the previous example, converting the binary value in MachineLaunchRestriction to a Win32 security descriptor. This example returns only which accounts have permission to access DCOM on the server Server01. It does not return what specific permissions those accounts have.
$strcomputer = “Server01” $ConverterObject = @{ TypeName = “System.Management.ManagementClass” ArgumentList = “Win32_SecurityDescriptorHelper” } $Reg = [WMIClass]“\$strcomputer ootdefault:StdRegProv” $DCOM = $Reg.GetBinaryValue(2147483650,` “softwaremicrosoftole”,“MachineLaunchRestriction”).uValue $Converter = New-Object @ConverterObject $DCOMDescriptor = ($Converter.BinarySDToWin32SD($DCOM)).Descriptor foreach ($DACL in $DCOMDescriptor.dacl) { $Permission = ($DACL.Trustee).Name Write-Output “$Permission has DCOM permission on $strcomputer” }
You can display the specific DCOM access permissions each account has by parsing the discretionary access control list (DACL) objects returned from the previous example. These DACLs contain an access mask, which will need to be converted from the binary form to be readable. You can use a hashtable to hold the possible values, and use Windows PowerShell's bitwise and comparison operator to convert the Win32 security descriptor. The hashtable looks like this:
$DCOMConversion = @{} $DC0MConversion.Add(0x2,“Local Launch”) $DC0MConversion.Add(0x4,“Remote Launch”) $DC0MConversion.Add(0x8,“Local Activation”) $DC0MConversion.Add(0x10,“Remote Activation”)
An individual DACL access mask may be 19. Using the bitwise and operator would show that the account has Remote Activation and Local Launch permissions to DCOM. The typical DCOM security descriptor will have multiple DACLs listed. As you can see in the previous example, you use a loop to gather information on each DACL.
Note
If you need a refresher on the bitwise and comparison operator, see the help topic Get-Help about_Comparison_Operators.
The script in Listing 9-2 returns the accounts that have DCOM and WMI permission and the specific permission granted on a computer of your choosing. The script, when run without parameters, returns data for the local computer. When run with the optional Computer parameter, the script returns DCOM permissions for the remote computer specified.
LISTING 9-2 Get-DCOMPermission.ps1
Param ( [string] $Computer = “.”, [System.Management.Automation.PSCredential] $Credential = $null ) $DC0MConversion = @{} $DC0MConversion.Add(0x2,“Local Launch”) $DC0MConversion.Add(0x4,“Remote Launch”) $DC0MConversion.Add(0x8,“Local Activation”) $DC0MConversion.Add(0x10,“Remote Activation”) $WMIConversion = @{} $WMIConversion.Add(0x1,“Enable”) $WMIConversion.Add(0x4,“Full Write”) $WMIConversion.Add(0x2,“Method Execute”) $WMIConversion.Add(0x8,“Partial Write Rep”) $WMIConversion.Add(0x20,“Remote Enable”) $WMIConversion.Add(0x10,“Write Provider”) $WMIConversion.Add(0x20000,“Read Control”) $WMIConversion.Add(0x40000,“Write Dac”)
$ConverterObject = @{ TypeName = “System.Management.ManagementClass” ArgumentList = “Win32_SecurityDescriptorHelper” } $ACLObject = @{ Property = “Computer”,“Name”,“Type”,“Permission” } $WmiObject = @{ ComputerName = “$Computer” Namespace = “root/cimv2” Class = “ SystemSecurity” } if ($Credential) { $Object = @{ List = $True Namespace = “rootdefault” ComputerName = $Computer Credential = $Credential } $Filter = @{ FilterScript = {$_.name -eq “StdRegProv”} } $Reg = Get-WmiObject @Object | Where-Object @Filter $Security = Get-WmiObject @WmiObject -Credential $Credential } else { $Reg = [WMIClass]“\$Computer ootdefault:StdRegProv” $Security = Get-WmiObject @WmiObject } $DCOM = $Reg.GetBinaryValue( 2147483650,“softwaremicrosoftole”, “MachineLaunchRestriction”).uValue $Converter = New-Object @ConverterObject $binarySD = @($null) $result = $Security.PsBase.InvokeMethod(“GetSD”,$binarySD) $DCOMDescriptor = ($Converter.BinarySDToWin32SD($DCOM)).Descriptor $WMIDescriptor = ($converter.BinarySDToWin32SD($binarySD[0])).Descriptor $RightsCollection = @() foreach ($DCOMDACL in $DCOMDescriptor.dacl) { if ($DCOMDACL.AceType -eq 0) { $Perms = @()
foreach ($key in $DC0MConversion.keys) { if ($DC0MDACL.AccessMask -band $key) { $Perms += $DC0MConversion[$key] } } $Perm = ($Perms | ForEach-0bject -Process {$_.ToString()}) -join “,” $Permission = ($DC0MDACL.Trustee).Name $Perms0bject = “” | Select-0bject @ACL0bject $Perms0bject.Computer = $Computer $Perms0bject.Name = ($DC0MDACL.Trustee).Name $Perms0bject.Type = “DC0M” $Perms0bject.Permission = $Perm $RightsCollection += $Perms0bject } } foreach ($DACL in $WMIDescriptor.dacl) { if ($DACL.AceFlags -eq 0) { $Perms = @() foreach ($key in $WmiConversion.keys) { if ($DACL.AccessMask -band $key) { $Perms += $WMIConversion[$key] } } $Perm = ($Perms | ForEach-0bject -Process {$_.ToString()}) -join “,” $Perms0bject.Computer = $Computer $Perms0bject.Name = ($DACL.Trustee).Name $Perms0bject.Type = “WMI” $Perms0bject.Permission = $Perm $RightsCollection += $Perms0bject } } Return $RightsCollection
If you need to pass credentials to the remote computer, you can use the optional Credential parameter. The following example retrieves the DCOM permissions from the computer Server01 using the credentials of the user contosojohnb:
Get-Credential -Credential contosojohnb .Get-DC0MPermission.ps1 -Computer “Server01” -Credential $cred
The following example retrieves the DCOM permissions from the computer Mailbox01 using the credentials of the current user:
.Get-DCOMPermission.ps1 -Computer “Mailbox01”
By default, DCOM is enabled for members of the local administrators group. Running scripts with this level of permission could provide a huge security risk. Granting a domain user account remote DCOM access allows data gathering without exposing the servers to this security risk.
The script in Listing 9-3 configures DCOM and WMI permissions on the server Exch2010 to allow the domain user contosourtb to run WMI queries in the rootcimv2 namespace and perform other tasks via remote DCOM. This example could easily be modified to provide WMI access to other namespaces and to operate on multiple computers. This example requires that the type accelerators from Listing 9-1 be loaded prior to running the example.
LISTING 9-3 Set-DCOMPermission Script
function Get-Sid { Param ( $DSIdentity ) $ID = New-Object -TypeName System.Security.Principal.NTAccount($DSIdentity) return $ID.Translate([System.Security.Principal.SecurityIdentifier]).toString() } $Server = “Exch2010” $regHive = [reghive]“LocalMachine” $sid = Get-Sid “contosourtb” $keyName = “softwaremicrosoftole” $ValueName = “MachineLaunchRestriction” $SDDL = “A;;CCWP;;;$sid” $DCOMSDDL = “A;;CCDCRP;;;$sid” $regKey = [regkey]::OpenRemoteBaseKey($regHive,$Server) $DCOMKey = $regKey.OpenSubKey($keyName,$True) $DCOM = $DCOMKey.GetValue($ValueName) $SecurityObject = @{ ComputerName = $Server Namespace = “root/cimv2” Class = “_SystemSecurity” } $Security = Get-WmiObject @SecurityObject $ConverterObject = @{
TypeName = “System.Management.ManagementClass” ArgumentList = “Win32_SecurityDescriptorHelper” } $Converter = New-0bject @Converter0bject $binarySD = @($null) $result = $security.PsBase.InvokeMethod(“GetSD”,$binarySD) $outsddl = $converter.BinarySDToSDDL($binarySD[0]) $outDC0MSDDL = $converter.BinarySDToSDDL($DC0M) $newSDDL = $outsddl.SDDL += “(“ + $SDDL + ”)” $newDC0MSDDL = $outDC0MSDDL.SDDL += “(“ + $DC0MSDDL + ”)” $WMIbinarySD = $converter.SDDLToBinarySD($newSDDL) $WMIconvertedPermissions = ,$WMIbinarySD.BinarySD $DC0MbinarySD = $converter.SDDLToBinarySD($newDC0MSDDL) $DC0MconvertedPermissions = ,$DC0MbinarySD.BinarySD $result = $security.PsBase.InvokeMethod(“SetSD”,$WMIconvertedPermissions) $DC0MKey.SetValue($ValueName, $DC0MbinarySD.binarySD)
In this chapter, you learned to manage Windows services and processes locally and remotely. You examined how to read and write to the registry on the local computer as well as remote computers. You examined and modified network settings, discovered and retrieved performance counters, and extended your knowledge of the registry to allow you to modify regional settings on remote computers. You learned to manage local groups and users on remote computers. Finally, you also examined DCOM permissions on the local and remote computers.
In the next chapter, you work with Active Directory. You learn the prerequisites for installing the module. Once your computer meets the requirements, you load the module and learn to query Active Directory objects. You administer users and groups, and manage service accounts and organizational units. You also examine password policies.
3.143.4.181