CHAPTER 9

Performing Advanced Server Management

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.

Managing Command-Line Services

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.

Listing Running Services on Multiple Servers

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
...

Finding Servers Running a Specific Service

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

Listing Stopped Services That Are Set to Start Automatically

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
}
SystemName                                          Name
----------                                          ----
FileServer01                                        Ati External Event Utility

FileServer01                                        clr_optimization_v4.0.30319_32
FileServer01                                        clr_optimization_v4.0.30319_64
FileServer02                                        NetTcpActivator
FileServer02                                        sppsvc

Starting Stopped Services

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
}
}

Setting Services to Disabled

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.

Managing Processes

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.”

Listing All Processes on Multiple 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

Stopping Processes on Remote Servers

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.

Note

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. image

Reading the Registry

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.

Using the Registry Provider Locally

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

Using Microsoft.Win32.RegistryHive Remotely

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

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.

TABLE 9-1 Registry Value Types

image

Besides setting values for existing registry keys, Windows PowerShell provides methods to create new registry keys.

Locally Using the Registry Provider

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. image

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

Remotely Using Microsoft.Win32.RegistryHive

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. image

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)
}

Validating Network Configuration on Remote Servers

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.

Retrieving the DNS Settings

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*

Validating That Servers Use the Same DNS Settings

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

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. image

Modifying the DNS Suffix Search Order

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. image

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 the Server's IP Address

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)
}

Gathering Data from Performance Counters

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.

Modifying Regional Settings on Multiple Computers

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)
}
}

Managing Local Accounts

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

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.image

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. image

Creating and Deleting Local Users and Groups

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”)

Configuring Remote DCOM

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.

Viewing DCOM Permissions

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. image

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”

Granting a Domain User Remote DCOM Access

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)

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181