Expert Solutions: Advanced Event 4 of the 2010 Scripting Games

Bookmark and Share

 

(Note: These solutions were written for Advanced Event 4 of the 2010 Scripting Games.)
 

Advanced Event 4 (Windows PowerShell)

 


Photo of Matt Johnson

Matt Johnson is a system administrator and founder of the Southeast Michigan PowerShell Script Club. He holds numerous certifications including MCSE and GSEC. You can find his blog at http://www.mwjcomputing.com/blog, or visit the Southeast Michigan PowerShell Script Club’s site at http://www.michiganpowershell.com. You can find him most days lurking around the Internet and trying to solve problems using Windows PowerShell.

 

When I sat down to write the solution for this event, I did what I normally do and broke it down into smaller “byte sized” pieces (nyuk nyuk). From first glance, this problem looks straightforward, and I found 4 pieces to break it into.

·         Get and add an environment variable.

·         Write information to log files.

·         Run this against remote computers.

·         Add a silent mode.


When I looked at the pieces, the easiest parts seemed to be writing the information to log files and adding the silent mode. So let’s work with the two more complicated parts.


First up, we will tackle writing and getting environment variables. The easiest way to do this is use the $env provider. However, when working on this script, it became evident that that won’t work on a remote computer, and we will need to use the [System.Environment] .NET Framework class. (You will notice this hint in the 2010 Scripting Games Study Guide.) The two methods we will concentrate on are the GetEnvironmentVariable and SetEnvironmentVariable.


To get the environment variable, you would use the command:

                [System.Environment]::GetEnvironmentVariable(Name,Type)


To set the environment variable, you would use the command:

      [System.Environment]::SetEnvironmentVariable(Name,Value,Type)


If you worked with environment variables before, you are probably used to User and System variables. However, for some reason the .NET Framework class calls system variables Machine variables. The script accepts User as well as both System and Machine as types, which is seen using the ValidateSet switch in our parameter area. (You can see more of the additional parameters by typing
Get-Help about_functions_advanced.)

[ValidateSet(“User”, “Machine”, “System”)]
      [string]$Type = “Machine”,
   


Later, there are a few lines in there to convert System to Machine so that we do not have any issues when using the .NET Framework class.


With this in hand, we need to run this remotely against other computers. This is where Windows PowerShell 2.0’s Invoke-Command cmdlet comes into play. (Keep in mind that all machines should be configured to allow remoting ahead of time.) Because part of the problem is that you need to accept environment variables that the user can define and not just the default, we need Invoke-Command to accept parameters. We accomplish this by using script blocks. Here are the two script blocks I passed to Invoke-Command to perform the operations:

$getCommand = {
      param($Name, $Type)
      Invoke-Command {[System.Environment]::GetEnvironmentVariable($Name,$Type)}   
}

$setCommand = {
      param($Name, $Value, $Type)
      Invoke-Command {[System.Environment]::SetEnvironmentVariable($Name,$Value,$Type)}
}


As you can see, both script blocks accept the parameters that we need to set the information on the remote computer. Now all we need to do is run the following Invoke-Command commands so that our parameters are passed to the script blocks. The variables we pass are the ones that are defined in the main script parameter area so that we don’t lose track of the values:

Invoke-Command $setCommand -ComputerName $server -ArgumentList $Name, $Value, $Type
Invoke-Command $getCommand -ComputerName $server -ArgumentList $Name, $Type


The last part that may cause some confusion is “silent” mode. This is accomplished by adding a parameter to our script of type [Switch], as shown here.

param (
      [string]$Name = “ScriptingGuys”,         
      [string]$Value = “ScriptingGuys.com”,    
[ValidateSet(“User”, “Machine”, “System”)]
      [string]$Type = “Machine”,
      [switch]$Silent
)


Then later in our code when we go to set the environment variable, we will check to see if $Silent is true, and act accordingly.


As you can see here, we run the script both regularly and with silent mode. I also get the content of the logs, so you can take a look at how I logged things.

Image of running script regularly and with silent mode


Here is the full script:

set-envvariable.ps1

# Script: set-envvariable.ps1
# Author: Matt Johnson – powershell@mwjcomputing.com
# Description: Microsoft 2010 Scripting Games – Advanced – Event 4
#Requires -version 2.0

param (
      [string]$Name = “ScriptingGuys”,          # Name of environment variable
      [string]$Value = “ScriptingGuys.com”,     # Value of environment variable
      [ValidateSet(“User”, “Machine”, “System”)]
      [string]$Type = “Machine”,                      # Type of environment variable
      [switch]$Silent                                       # Silent switch
)

# Get list of workstations/servers to run against.
$serverList = Get-Content “C:fsodataservers.txt”

# Get current time.
$time = Get-Date

# Set logging locations
# Log of variable setting actions
$scriptLog = ‘c:fsodataset-envvariable-‘ + (Get-Date -format ‘yyyy-MM-dd’) + ‘.txt’
# Log of changes when -Silent switch is used.
$silentLog = ‘c:fsodataset-envvariable-silent-log-‘ + (Get-Date -format ‘yyyy-MM-dd’) + ‘.txt’

# Command to get the environment variable on the machine.
$getCommand = {
      param($Name, $Type)
      Invoke-Command {[System.Environment]::GetEnvironmentVariable($Name,$Type)}   
}

# Command to set the environment variable on the machine.
$setCommand = {
      param($Name, $Value, $Type)
      Invoke-Command {[System.Environment]::SetEnvironmentVariable($Name,$Value,$Type)}
}

# Fix type of variable if System is entered.
if ($Type -eq “System”) {
      $Type = “Machine”
}    

# Loop through each server
foreach ($server in $serverList)
{    
      # Get current environment variable value
      $currentValue = Invoke-Command $getCommand -ComputerName $server -ArgumentList $Name, $Type
     
      # Check to see if variable is null
      if ($currentValue -eq $null) {
            # If variable is null, set the environment variable on the remote computer.
            try {
                  # Execute value on remote server.
                  Invoke-Command $setCommand -ComputerName $server -ArgumentList $Name, $Value, $Type
                  # Write information to log file
                  “Added – $server – $time” | Out-File -FilePath $scriptLog -Append
            } catch {
                  # Display error message to user.
                  “Error has occurred while adding value to $server.”  
            }
      } else {
            # Check to see if the current value is the value that we are trying to set.
            if ($Value -ne $currentValue) {
                  # Check for silent switch
                  if (!($Silent)) {
                        # Prompt user to see if user wants to change the value.
                        $userPrompt = Read-Host -Prompt “Do you want to change the old value of ‘$currentValue’ to $Value on $server”
                       
                        # Check to see if value is either y or n. If not, prompt again for input.
                        while (($userPrompt -notlike ‘y’) -and ($userPrompt -notlike ‘n’)) {
                              $userPrompt = Read-Host -Prompt “INVALID ENTRY – Do you want to overwrite the old value of ‘$currentValue’ on $server”
                        }
                       
                        # If Y, Overwrite value.
                        if ($userPrompt -match “[yY]”) {
                              try {
                                    # Execute value on remote server.
                                    Invoke-Command $setCommand -ComputerName $server -ArgumentList $Name, $Value, $Type
                                    # Write information to log file
                                    “Updated – $server – $time” | Out-File -FilePath $scriptLog -Append
                              } catch {
                                    # Display error message to user.
                                    “Error has occurred while updating value on $server.”
                              }
                        } else{
                              # If user said no to changing the value, log to file.
                              “No Change – $server – $time – $currentValue” | Out-File -FilePath $scriptLog -Append
                        }
                       
                        # Reset user prompt.
                        $userPrompt =$null
                  } else {
                        try {
                              # Execute value on remote server.
                              Invoke-Command $setCommand -ComputerName $server -ArgumentList $Name, $Value, $Type
                              # Write information to log file
                    “Updated – $server – $time” | Out-File -FilePath $scriptLog -Append
                              # Write information to silent log file.
                              “$server – $currentValue – $Value – $time” | Out-File -FilePath $silentLog -Append
                        } catch {
                                    # Display error message to user.
                                    “Error has occurred while updating value on $server.”
                        }
                  }
            } else {
                  # Log to file that environment variable value was already correct on machine.
            “Correct – $server – $time” | Out-File -FilePath $scriptLog -Append
        }
      }
}

 

Advanced Event 4 (VBScript)

Photo of Clint Huffman