Nuts and Bolts PowerShell Lead image: Lead Image © Maurizio Ascione, Fotolia.com
Lead Image © Maurizio Ascione, Fotolia.com
 

Client management in the domain using PowerShell

On My Mark

PowerShell offers the perfect client management solution using DIY scripts and a simple storage solution. By Thomas Wiefel

Tools such as System Center Configuration Manager, opsi-winst, or Puppet offer client management, but the learning curve can be huge. Instead, a flexible combination of DIY scripts and a simple storage solution offers a perfect solution. In this article, I automate various client management tasks in a domain with PowerShell with a focus on the following areas:

I am using Windows 10 client with a Windows Server 2012 R2 domain, with PowerShell v5 on the client and PowerShell v4 on the server.

Administration via a CSV File

In PowerShell remoting, a distinction is made between 1:1 and 1:n (fan-out) relationships. 1:1 remote management is implemented through the PsSession concept. Fan-out remote management – that is, simultaneous processing of instructions on multiple target computers – is achieved through invoke-Command and its parameters. In this process, separating the configuration settings from the programming logic is useful for maintaining the script. The data can be stored in a database, or an XML or text file.

In particular, a structured, comma-separated values (CSV) file provides a simple and practical approach. It is easily maintained – even for administrators with less scripting experience. The columns in the CSV table include the IP address of the Windows client, the DNS servers, desired Windows features, printers, required group memberships, and optionally other control information. The script is executed remotely via a central management computer. This eliminates the need for time-consuming local modification of individual clients. On running the script, you may need to pass in the required authorization context via the -Credential parameter. In addition to PowerShell cmdlets and Windows Management Instrumentation (WMI), you also use classes from the System.Directory.DirectoryService namespace for processing.

Before the automated configuration begins, you need to import the value store. The Import-Csv -Path path to the CSV file gives you hash-value table. The defined column names act as indexes for accessing the values. A foreach loop iterates through the rows. For each line, this returns the intermediate variables $Item.IP, $Item.DNS, $item.FEATURE,Item.GROUPS, and Item.PRINTERS. For simplicity's sake, I will be outputting individual values for each column in this article. When saving multiple values per column, it is a good idea to choose a delimiter that differs from the selected column separator. The pipe character | is a good choice. The field can be broken down and processed with the string operation $item.columnname.split("|").

This information needs to be put in the context of the processing functions. However, to configure the client, you first need working network connectivity. For this reason, the first auxiliary function checks the availability of the computer and quits with an exit code if it is not given. The use of ping.exe is not effective. Its output, in the form of a string, depends on the language version, which makes the analysis tricky. It is far better to use:

test-Connection -ComputerName -ComputerName Item.IP

The output takes the form of a unique Boolean type ($TRUE, $FALSE):

if (test-Connection -ComputerName -ComputerName Item.IP){..}else{exit 1}"

Establishing Trust

If your company uses a directory service, you also need other data besides the client's IP address. Therefore, auxiliary functions determine the host-specific variables from the IP address. Remote access to the client using

invoke-Command -ScriptBlock {...} -credential $cred -ComputerName Item.IP

needs some preliminary work. After all, PowerShell remoting is based on mutual authentication. This is possible via the client, but not via the IP address. If you want PowerShell to trust the remote site, you need to enter the IP addresses of all the target computers in the Trusted Hosts field up front:

Set-Item WSMAN:\localhost\Client\TrustedHosts IP,IP2,etc

Determining the fully qualified domain name (FQDN) is not as trivial as it first appears. In one approach, the environment variables are mapped in the corresponding PsDrive ENV:. However, a combination of $env:ComputerName and $env:UserDomain does not make sense. The variable ComputerName contains the NetBIOS name, and UserDomain is filled with the local computer name except when the user is logged into a domain. These are two key limitations that you have to deal with. However, you can use the FQDN to determine the actual computer name and the domain with the following operations:

$Field = $FQDN.Split("."); $LDAPName = "cn=" + $Field[0]; $SimpleName = $Field[0]

Based on these values, you can start with the further processing steps. If the client is not a member of the domain, you can add it:

Add-Computer -DomainName MyDom.com -Credential Administrator@MyDom.com

Printer Management

Printer management is essentially the premise of WMI and the parent Common Information Model (CIM) classes up to PowerShell v4. Executability is not restricted by any PowerShell version (see Listing 1).

Listing 1: Printer management executability

$Drucker = "HP DeskJet 840C/841C/842C/843C"
$ObjWMI = get-WmiObject -class Win32_Printer -Filter "name = '$drucker'";
$status = $ObjWMI.Printer.Status
switch ($status) {
"3" {"Printer is ready"}
"4" {Printer working"}
"5" {"Printer starting up"}
"6" {"Print job finished"}
"7" {"Printer is offline"}
Default {"Unknown status"}
}

As of PowerShell v4, there are command families for printer management, grouped around three central nouns: Printer for access to local and network printers, Print Job to control print jobs, and Printer Driver for providing the required drivers as the basis for further printer management. However, administrative permissions must exist for providing the printer driver. Your task is to add a local printer driver. The add-printer driver cmdlet needs the name of the printer driver; the full command is:

Add-PrinterDriver -Name "Drivername" -ComputerName "Client"

You must pass in the name of the driver accurately, including any spaces; otherwise the instruction will just provoke an error message. The use of wildcards is not allowed, such as passing in ranges with [d-g] or using placeholders such as *. The get-Printerdriver cmdlet lists the available drivers.

If you need information, there is no alternative to creating an object, such as:

$Pr_DEf = (Get-Printer | ? {$_.Name -eq "MyPrinter"});
$Pr_DEf | Select-Object -Property Status, DriverName, Shared;

If you want this printer to be the default printer, choosing the WMI Win32_Printer class makes sense:

$Ac_PR = gwmi win32_printer -Filter "name =MyPrinter";
$Ac_PR.setDefaultPrinter();

Determining Group Membership

Object management in Active Directory (AD) can be handled via two interfaces. The PowerShell cmdlets are based on AD's web service. Working with this container's commands is easy and intuitive, but the performance is not very impressive. Also, the scope of services is not on a par with the classes in the System.DirectoryServices namespace. Here, just two classes share the task of processing the requests. System.DirectoryServices.DirectoryEntry addresses objects in AD. When creating an object, the parameters are the DistinguishedName of the object, a legitimate user account, and the corresponding password. Once the domain context has been determined, the second class can start its work.

The LDAP searcher can be loaded with very efficient search parameters. The search direction can be upwards ascending to the root of the domain. The choice of the search area, the complexity of the search pattern, and the search method affect the time it will take to populate the result field. Armed with this tool, you can quickly create a list of group memberships for a computer object:

$StrUserName = "Administrator@seminar.local";
$StrPasswd = "Pa`$`$w0rd2016";
$StrDom = "LDAP://dc=seminar,dc=local";

This stores the information for the connection with the domain. You can use the DirectoryEntry class and its constructor to save a domain context in the first step:

$context = New-Object System.DirectoryServices.DirectoryEntry($StrDom,$StrUserName,$StrPasswd);
$ComputerDN = "CN=mobil,cn=computers,DC=seminar,DC=local"
$DirectorySearcher = ([ADSISearcher] $context)
$DirectorySearcher.Filter = "(&(member:1.2.840.113556.1.4.1941:=$ComputerDN))"

The LDAP Searcher needs more information before it can start working. You can again define the starting point of the search via the constructor. Alternatively, you can use the Search Base property. The filter browses the directory based on criteria such as class, Common Name (CN), or the AD objects' other attributes. Any desired search directions can be used. The filter used here searches the ancestor axis for all groups of which the computer $ ComputerDN is a member.

In terms of the Searcher class methods, you have a choice between findOne() and findAll(). findOne() would stop searching after the first match; findAll() provides a complete summary field of group memberships for a computer:

$DirectorySearcher.Searchscope="subtree"
$DirectorySearcher.FindAll() |
  Foreach{
  $($_.Properties).cn
  }

You need to compare the summary with the desired group membership $item.GROUPS here:

$GROUP_MEMBER = $FALSE;
if($($_.Properties).cn -eq $item.GROUPS) {$GROUP_MEMBER = $TRUE;}

Setting DNS Records

The next task is entering DNS servers; the interface to the DNS configuration is the CIM class MSFT_DNSClientServerAddress. CIM provides a class structure that is superordinate to WMI. As of PowerShell v3, it is possible to use these classes directly using Get-CimInstance or Invoke-CimMethod. Parallel to this, PowerShell has increasingly bundled more CIM and WMI functions into cmdlets from version to version. The idea is to take the task of programming the object formation off the administrator's hands. Instead, a parameter-based command syntax can be used. This has also happened in this class; the implementation relies on a cmdlet:

Set-DnsClientServerAddress -InterfaceIndex 4 -ServerAddresses $item.DNS

If you are uncertain about the index number for configuring the network adapter, you can query the index number up front.

The following command determines the index number for the Ethernet NIC:

(Get-NetAdapter | where-Object -FilterScript {$_.InterfaceName-eq "Ethernet"}).InterfaceIndex

Installing Windows Features

Finally, the term PowerShell can mean different things on different computers. If you are upgrading a Windows 7 client to PowerShell v4 with the software development kit (SDK), the functionality is still not the same as with the PowerShell on Windows 8.1, despite having the same $Host.Version.

The management of WindowsOptionalFeatures is based on the DISM package. Since PowerShell is a classic consumer language, each command requires an underlying class. If a dynamic-link library (DLL) is not available, this rules out the associated instruction. Because the OS version in this example is higher than 7, the noun WindowsOptionalFeature exists. A listing using a getter provides the return values shown in Figure 1.

Get-WindowsOptionalFeature returns information on Windows features and their status.
Figure 1: Get-WindowsOptionalFeature returns information on Windows features and their status.

If you want the client to add a new feature, the cmdlet

Enable-WindowsOptionalFeature -FeatureName $ item.FEATURE

does the task reliably.

Conclusion

For many client management admin tasks, a combination of PowerShell remoting and Import-Csv or Import-CliXml is a perfect choice. The instructions can be scheduled for execution, if necessary, using a job trigger. Given carefully considered planning of the data structure, the functions, and processes – encapsulated in their own scripting modules where appropriate – you are already very close to a professional solution. It would also be conceivable to implement a View, for example, using reports in Excel or a web front end.