A previous article in this series reviewed the security environment to run PowerShell scripts. This article demonstrated how to convert Windows PowerShell commands into functions, how functions can be used in PowerShell scripts, and how to convert scripts into script modules.

One of the challenges when writing scripts is to make them portable and easy to use by other IT administrators and sometimes by people that may not be familiar with scripting but who need to perform routine maintenance, monitoring, and configuration tasks.

Writing a script allows you to organize all the commands that you want to execute in a text file so that the shell executes those commands in the proper order when you run the script. All PowerShell scripts are saved using a .ps1 file extension.

Using Commands

Let’s say that you want to query parameters and IP configuration settings from a computer in your network. The following command provides IP address, Mac address, default gateway, DNS server address and other important settings:

Get-CimInstance -ComputerName Server1 -ClassName ` Win32_NetworkAdapterConfiguration –Filter “IPEnabled = TRUE” |
Select-Object ` IPAddress,MacAddress,DefaultIPGateway,DNSServerSearchOrder |
Format-List

Even though the commands work fine and you get the expected results, there are several limitations with typing commands directly on the shell. First of all, you have to retype the commands again every time you try to get the same information from another computer. This not only makes the process more laborious, but also more prone to errors. The level of difficulty increases when you try to reach multiple computers at the same time. Delegating these tasks to an inexperienced user can easily be seen as an act of punishment instead of effective IT management and delegation. You got the point–we need a better approach.

Using Functions and Scripts

You know that the Get-CimInstance command generates the type of information that you need. To use that command on a script, it is important to recognize command values that can be parameterized because this allows you to use different values every time you run the script. In our example above, you may want to run the command against different computer names, either one at a time or grouping them in an array or text file.

Let’s create a basic function using one parameter for the computer name. A function, like a script, is a sequence of PowerShell commands. However, functions are assigned a name and placed within a script as a separate block of code that only runs when the script calls its name.

In the code below, a function name Computer-Info is created. The Param() block is used to name the parameters in the function.

Inside the block, you state a comma-separated list of parameters. Each parameter is really a variable; that is why the parameter names start with a dollar sign ($). It is recommended that you define the data type for each parameter. Data types include [string] for strings, [DateTime] for date and time, [int] for 32-bit signed integers. [bool] for True or False values, and more.

You can specify a default value for a parameter. A default value is recommended when the value does not need to change frequently and will typically be the same each time the script runs. It is also possible to configure a parameter as mandatory, in which case Windows PowerShell will prompt for a value when the scripts is executed. Default values are ignored when a parameter is set as mandatory.

 Function Get-ComputerInfo {
      
      Param(
           [string]$ComputerName
      )
 
 Get-CimInstance -ComputerName $computerName `
 -ClassName Win32_NetworkAdapterConfiguration `
 -Filter "IPEnabled = TRUE" |
  
 Select-object `  
 IPaddress,MacAddress,DefaultIPGateway,DNSServerSearchOrder
 
  }

If you place the above function into a script and run the script, nothing is going to happen. You must call the function from inside the script. In our case after the last curly brace, you need to type Get-computerInfo for the script to run the function. Usually this is done for testing purposes, especially when you plan to move the function into a script module, as you will see later.

Before creating the script module, the function above needs to be improved. A major limitation of this function is that it does not allow to query multiple computers executing single queries one computer at a time.

Function to Query Multiple Computers

Because the function must query multiple computers, it is necessary to provide some type of object enumeration. The ForEach construct is used in PowerShell to take a collection of objects, i.e., computers, and then let you run a block of one or multiple commands against those objects, one at a time.

The modified “Get-ComputerInfo” function below uses the ForEach construct and other advanced options to query one or multiple computers.

 Function Get-ComputerInfo {
    [CmdletBinding()] # Indicate this is an advanced function
     Param(
      [Parameter(Mandatory=$True,
                  ValueFromPipeline=$True,
                  ValueFromPipelineByPropertyName=$True,
                  HelpMessage='One or more computer names')]
      [Alias('Hostname')] # another name for ComputerName
      [string[]]$ComputerName 
  )

 PROCESS {
     foreach ($computer in $ComputerName) {
        Write-Verbose "Querying $computer"   	# DislayDisplay status
 								   	# messages as
 									# script runs.
         
    # The information is retrieved and saved into a variable 

          $IPConfig = Get-CimInstance –ComputerName `
 $computer -ClassName Win32_NetworkAdapterConfiguration `
 -Filter "IPEnabled = TRUE"

    # Use a hash table to expose specific values stored in the
    # $Ipconfig variable. 

  $properties = @{
   		    'ComputerName'=$computer;            
             'IPAddress' = $IPConfig.IPAddress;
             'DefaultGateway'= $IPConfig.DefaultIpGateway; 
             'MacAddress' = $IPConfig.MacAddress;
             'DNSServer' = $IPConfig.DNSServerSearchorder
				}
                           
  
  # Once the output has been defined into the hast table, next a
  # custom object is created. This new object will have the
  # properties from the hash table attached to it.    
  # 

 $output = New-Object -TypeName PSObject –Property $Properties   

 Write-Output $output
   


                }

        }
  }

  # The Next line of code is for testing purposes only. It will be 
  # removed when the function is converted to a script module later.

  Get-computerinfo -ComputerName Server1

In the above function, the CmdletBinding() attribute defines an Advanced PowerShell Function. CmdletBinding() lets you use Write-Verbose and Write-Debug to allow control of the script or function’s output using the –Verbose and –Debug switches. The parenthesis after CmdletBinding can stay empty, but sometimes they are needed to provision advanced features, like the SupportShouldProcess, which supports the use of –Confirm and –Whatif parameters.

The ComputerName parameter is mandatory, which means that at least one computer name must be entered for the function to run. That computer name does not need to be typed into the script or the shell, as the ValueFromPipeline option indicates that the function accepts pipeline input. This makes the function more flexible and versatile as you can pipe those computer names from a PowerShell command, or from another function. The [string[]] indicates that the computer names will be interpreted as Unicode characters and an array of multiple computer names are allowed.

The ValueFromPipelineByPropertyName will also work if you use the alias Hostname as a parameter instead of ComputerName when running the script.

ComputerName is defined as an array, the shell attaches one string at a time to the -ComputerName parameter and runs the PROCESS script block. The shell converts every single value into a one-item array that is enumerated by the ForEach construct running inside the PROCESS script block.

Because this function accepts pipeline input, it must be written to operate when the pipeline is used. For example: Get-content –Path C:\computers.txt | Get-ComputerInfo requires pipeline functionality. However, it is possible that no input is given via the pipeline; for instance: Get-Computerinfo –ComputerName Server1,Server2. The PROCESS script block allows the function to operate with or without pipeline input.

The hash table is the code that follows the at “@” sign and is enclosed between two curly braces. The hash table is stored in the $Properties variable. Typically, a hash table includes one or more key value pairs; however, it is possible to create an empty hash table that contains no key value pairs.

The New-Object cmdlet is used in our function to create a new PowerShell object and the content of the hash table is assigned as the properties for the new object.

The above function runs fine in a PowerShell script. You can make the same function easier to use by including it in a PowerShell script module.

Using PowerShell Script Modules

A script module is a Windows PowerShell script that contains one or more functions. When you save a script as a script module, PowerShell can load your module automatically and make your functions available as commands from the shell. This means that functions in the script module work like standard shell commands. Script modules must be saved with a .psm1 file name extension in one of the location defined on the PSModulePath environment variable.

This command allows you to verify the correct locations:

Get-Content Env:\PsModulePath

Besides using the right extension (.psm1) and location, a script module must be saved to a folder that has the same name as the module’s name. Module names must be unique.

For our demo, before saving the script module containing the Get-ComputerInfo function, a folder named AdminTools has been created in the following location:

C:\Program Files\WindowsPowerShell\Modules

Comment-based help is added to the script module to make it easier for users to understand how to use the Get-ComputerInfo function. You can access this comment-based help in the same way you access help on regular PowerShell cmdlets.

To create the script module, the script below is saved as AdminTools.psm1 to the following location:

C:\Program Files\WindowsPowerShell\Modules\AdminTools

 Function Get-ComputerInfo {
 <#
 .SYNOPSIS
 Retrieves Mac Address, DNS Server, IP Address, and Default
 Gateway from one or more computers.
 .DESCRIPTION
 This command retrieves specific information from each
 computer. The command uses CIM; it will only work with
 computers that have WinRM enabled and Windows
 Management Framework (WMF) 3.0 or later has been installed.
 .PARAMETER ComputerName
 One or more computer names, as strings. IP addresses are not 13 accepted as valid for this parameter.
 You should only use canonical names from Active Directory. 
 This parameter accepts pipeline input.  
 .EXAMPLE
 Get-Content computers.txt | Get-ComputerInfo
 This example assumes that the computers.txt file includes one 
 computer name per line, and will retrieve information from 
 each computer listed. 
 .EXAMPLE
 Get-ComputerInfo -ComputerName Server1
 This example retrieves information from one computer.
 .EXAMPLE
 Get-ComputerInfo -ComputerName Server1,Server2,Win81A
 This example retrieves information from multiple computers.
 #>
 
    [CmdletBinding()] # Indicate this is an advanced function
     Param(
      [Parameter(Mandatory=$True,
                  ValueFromPipeline=$True,
                  ValueFromPipelineByPropertyName=$True,
                  HelpMessage='One or more computer names')]
      [Alias('Hostname')] # another name for ComputerName
      [string[]]$ComputerName 
  )

 PROCESS {
     foreach ($computer in $ComputerName) {
        Write-Verbose "Querying $computer"   	# DislayDisplay status
 							# messages as
 							# script runs.
         
    # The information is retrieved and saved into a variable 

          $IPConfig = Get-CimInstance –ComputerName `
 $computer -ClassName Win32_NetworkAdapterConfiguration `
 -Filter "IPEnabled = TRUE"

    # Use a hash table to expose specific values stored in the
    # $Ipconfig variable. 

  $properties = @{
   		    'ComputerName'=$computer;            
             'IPAddress' = $IPConfig.IPAddress;
             'DefaultGateway'= $IPConfig.DefaultIpGateway; 
             'MacAddress' = $IPConfig.MacAddress;
             'DNSServer' = $IPConfig.DNSServerSearchorder
				}
                           
  
  # Once the output has been defined into the hast table, next a
  # custom object is created. This new object will have the
  # properties from the hash table attached to it.    
  # 

 $output = New-Object -TypeName PSObject –Property $Properties   

 Write-Output $output
   


                }

        }
  }

To test the function, enter the following command:

Get-ComputerInfo Server1,Server2

To verify that the AdminTools script module was automatically loaded to the PowerShell session, enter:

Get-Module

The following commands will provide help on using the Get-ComputerInfo function:

 Get-Help ComputerInfo – Full
 Get-Help ComputerInfo –Examples

Closing Remarks

By scripting with Windows PowerShell, you can automate many tasks and reach levels of efficiency that would not be possible if you are limited to using PowerShell just as an interactive prompt. Packaging PowerShell commands into script modules makes it easier for co-workers and other IT administrators to perform their jobs using Windows PowerShell. Even people with less technical expertise may be able to run the script modules without having to understand the complexity of the commands involved.