Monthly Archives: May 2013

Installing Windows PowerShell 3.0 – Part 2

My goal is to use PowerShell scripts to download installation packages, query the computers in selected OU to establish which computers need an updated version of Microsoft Framework and finally to install the latest version of PowerShell on remote computers using PowerShell one-to-many remoting technique.

Download installation packages

The only option here is to use Microsoft .NET Framemowrk’s System.Net.WebClient class in Windows Powershell because PowerShell doesn’t have native cmdlets to download data from the Internet.

# Filename: FileDownload.ps1
# Author:   Alex Dujakovic
# Description: use index number to select lines from the UrlList.txt to be downloaded
# For example, use -Index 0,1,5 to select the first, the second,and the fifth line
# ***********************************************************************************

$webclient = New-Object System.Net.WebClient
$downloadList = (Get-Content "C:\Test\UrlList.txt" |Select-Object -Index 3, 5 )
$folder = "C:\Test\Download"

foreach($item in $downloadList)
	#Get the filename
	$filename = [System.IO.Path]::GetFileName($item)

	#Create path to downlod folder and file
	$file = Join-Path -Path $folder -ChildPath $filename

    Write-Host -NoNewline "Downloading file: [$item] - Saving file at: [$file]"

	#Download the file using the WebClient
	$webclient.DownloadFile($item, $file)

	Write-Host "DONE!" -ForegroundColor Red

In the script above, I use PowerShell’s New-Object cmdlet to create a System.Net.WebClient object and assign it to the $webclient variable. Then I set two variables, one for a download list and the second for the location of a download folder. The download list line in the script is very important:

$downloadList = (Get-Content “C:\Test\UrlList.txt” |Select-Object -Index 3, 5).

The –Index parameter is used to determine which line from a file will be selected and to create a collections or an array of items to be downloaded.  The UrlList.txt file looks like this:

UrlListI selected the second and the third line to download Microsoft .NET Framework 4 and Windows Management Framework 3.0 (includes PowerShell 3.0) , but in some other situations when I have for example ten lines in the file I could use the following as well:

Get-Content “C:\Test\UrlList.txt” |Select-Object -Index 1, 3, 5      # download line 2, 4 and 6

Get-Content “C:\Test\UrlList.txt” |Select-Object –First 2      # download first two items/lines

Get-Content “C:\Test\UrlList.txt” |Select-Object –Last 2       # download last two items/lines

DownloadThe above picture shows the result of FileDownload.ps1 script.

Establish which computers need PowerShell version 3.0

In selected OU, I have computers installed with Windows 7; a few have Windows 8 and some of them still have Vista OS. I need to check for the prerequisites such as the operating system, service pack level and .NET framework.

The following table summarizes the most recent operating system version numbers and I will use it to create a PowerShell script.

Table_osFirstly, I will execute a PowerShell command to create a file with the names of all the computers in the selected OU (remove –SearchBase parameter to get a list of all computers in a domain).

Import-Module ActiveDirectory

Get-ADComputer –Filter *

-SearchBase“OU=Computers,OU=AlexFirstLocation,DC=AlexTest,DC=Local”  |

Select-Object –ExpandProperty Name | Out-File –FilePath C:\Test\MyPcList.txt –Encoding ASCII

Secondly, I will run the script named:  GetNETversionReport.ps1 to establish the prerequisites (OS, service pack level and .NET framework) for the installation. The script will help me to sort all the computers in selected OU into the following groups by creating a log .CSV file.

Table_groupsIf you have computers with Windows 7 OS, SP1 and .NET Framework 4 installed than you will end up with just a few groups (most likely ‘Ready’ and ‘Retry’).  In my case, as I’ve mentioned above, I have different Windows OS versions and I need this script to sort them out with creation of a .CSV log file. All log files produced by the script (this .CSV files as well) will have the same name as the name of log file typed in as –Logfile parameter.  At the end, the script will display its findings in the form of HTML Report.

# Name: GetNETversionReport.ps1
# Script checks if remote machine has the prerequisites: OS, SP level and .NET framework
# Author: Alex Dujakovic, April 25, 2013

<#  .SYNOPSIS  Check for the prerequisites: OS, SP level and .NET framework on one or more remote computers  .EXAMPLE  GetNETversionReport.ps1 -computername (Get-Content C:\computerList.txt) -logfile C:\Logs\MyLog.log  #>



            {del $logFile -EA SilentlyContinue}

              Foreach ($computer in $computerName)
                        Try {

                                $OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer -EA Stop
                                        {$OS.Version -gt 6.2} {"$computer,Installed" | Out-File $logFile -append; break}
                                        {$OS.Version -gt 6.1}
                                                If($OS.ServicePackMajorVersion -gt 0)
                                                    {$ComputerSPver = $True}
                                                If(Get-WmiObject -Class win32_Product -ComputerName $computer | Where {$_.Name -match ".NET Framework 4"})
                                                    {$NETversion = $True}
                                                If($ComputerSPver -AND $NETversion)
                                                    {"$computer, Ready" | Out-File $logFile -append; break}
                                                    {"$computer,SP-Upgrade" | Out-File $logFile -append; break}
                                                    {"$computer,NET-Upgrade" | Out-File $logFile -append; break}

                                        {$OS.Version -lt 6.1} {"$computer,Unfit" | Out-File $logFile -append; break}
                                        Default {"$computer,Unknown" | Out-File $logFile -append}
                            }Catch {
                                         {"$computer, Retry" | Out-File $logFile -append}
                            $ComputerSPver = $Null
                            $NETversion = $Null

function ConvertLogToCsvFile {


        Get-Content $txtFile |
        # Foreach -Object {$_ -Replace "", ";"}  /--> To replace columns in text file with semicolon
        # Select-Object -Skip 1|                 /--> To skip the first line of the log file
        ConvertFrom-Csv -Header Computer, Status -Delimiter "," |
        Sort-Object Status |
        Export-Csv $csvFile -NoTypeInformation

function ConvertCsvToHtmlFile {

        # Format the output for ConvertTo-HTML
        $formatOutput = "</pre>
        $formatOutput = $formatOutput + "TABLE{border-width: 1px;border-style: collapse;border-color:black;}"
        $formatOutput = $formatOutput + "Table{background-color:white;border-collapse: collapse;}"
        $formatOutput = $formatOutput + "TH{border-width:1px;padding:0px;border-style:solid;border-color:black;background-color:#C0C0C0;}"
        $formatOutput = $formatOutput + "TD{border-width:1px;padding-left:5px;border-style:solid;border-color:black;}"
        $formatOutput = $formatOutput + "
 Import-csv $sourceFile |
 ConvertTo-HTML -head $formatOutput |
 Out-File $outputFile
 Invoke-Item $outputFile

$reportCsvFile = $logFile.Replace(".txt",".csv")
$reportHtmlFile = $logFile.Replace(".txt",".html")

ConvertLogToCsvFile -txtfile $logFile -csvfile $reportCsvFile
ConvertCsvToHtmlFile -sourcefile $reportCsvFile -outputfile $reportHtmlFile

Install PowerShell on remote computers

Prior to the installation, I have to make sure that PowerShell Remoting has been enabled on the computers in selected OU. Most client versions of Windows (Windows Vista, Windows 7, and so on) do not enable incoming Remoting connections by default, so my first step will be to enable it using Group Policy.

The following configuration pieces must be set in order for everything to work correctly:

  • The Windows Remote Management service
  • Windows Firewall exceptions
  • Credential delegation (CresSSP)
  • WinRM Client parameters
  • WinRM Service parameters

Windows Remote Management Service

  1. Navigate to Computer Configuration > Windows Settings > Security Settings > System Services
  2. Locate the Windows Remote Management (WS-Management) service and double-click it
  3. Tick the check box nexte to Define this policy setting and select Automatic. Click “OK”

Windows Firewall Exceptions

  1. Navigate to Computer Configuration > Windows Settings > Security Settings> Windows Firewall with Advanced Security > Windows Firewall with Advanced Security – LDAP://{GPO-DistinguishedName} > Inbound Rules
  2. Right-click the pane at the right and choose New Rule…
  3. Select Predefined and choose Windows Remote Management from the drop-down list. Click “Next”
  4. Remove the tick next to Windows Remote Management – Compatibility Mode (HTTP-In), but leave the one for Windows Remote Management (HTTP-In). The “Compatibility Mode” rule provides an upgrade path for systems using WinRM prior to version 2.0 and should not be enabled unless there is a specific need for it. Click “Next”
  5. Select Allow the connection and click “Finish”

WinRM Service Parameters

  1. Navigate to Computer Settings > Administrative Templates > Windows Components > Windows Remote Management (WinRM) > WinRM Service
  2. Double-click Allow automatic configuration of listeners
  3. Select Enabled
  4. In the box labeled IPv4 filter, enter a comma-separated list of IP address ranges to specify to which IP addresses the WinRM service should bind on the server. For example, would allow the WinRM service to bind to network adapters with an IP address in that range, but no other adapter.
  5. Do the same for IPv6 filter, using IPv6 addresses instead, or leave it blank to disable WinRM over IPv6
  6. Click “OK”
  7. Double-click Allow CredSSP authentication
  8. Select Enabled
  9. Click “OK”

Credential Delegation

  1. Navigate to Computer Settings > Administrative Templates > System > Credentials Delegation
  2. Double-click Allow Delegating Fresh Credentials
  3. Select Enabled
  4. Click “Show…”
  5. Enter a list of service principal names representing hosts to which clients should be allowed to delegate credentials. Wildcards are allowed in the host name portion of the SPN. For example:
    • WSMAN/Server01 — Allows delegation only to the server named Server01, and only using its single-label name
    • WSMAN/*.mydomain.local — Allows delegation to any host on the mydomain.local DNS domain, using their fully-qualified domain names only
    • WSMAN/* — Allows delegation to any host by any name
  6. Click “OK”
  7. Click “OK”

WinRM Client Parameters

  1. Navigate to Computer Settings > Administrative Templates > Windows Components > Windows Remote Management (WinRM) > WinRM Client
  2. Double-click Allow CredSSP authentication
  3. Select Enabled
  4. Click “OK”
  5. Double-click Trusted Hosts
  6. Select Enabled
  7. In the box labeled TrustedHostList, enter a comma-separated list of hosts the client should trust. Wildcards are allowed, and there is a special <local> value meaning trust all single-label names. For example:
    • *.mydomain.local — Trust any host on the mydomain.local DNS domain, using their fully-qualified domain names only
    • * — Trust any host by any name
  8. Click “OK”

Once the PowerShell Remoting has been enabled via GPO, I could use the following script to test it:

# Name: TestPsRemoting.ps1
# Script checks if remote machine is online and restarts it using command line tool
# Author: Alex Dujakovic, April 25, 2013

<#  .SYNOPSIS  Test PS Remoting on one or more remote computers  .EXAMPLE  'machineName-001' | TestPsRemoting  .EXAMPLE  TestPsRemoting -computername (Get-Content C:\computerList.txt)  #>

 foreach ($computer in $computername) {
    If(Test-Connection -ComputerName $computer -Quiet -Count 1)
                $errorActionPreference = "Stop"
                Invoke-Command -ComputerName $computer -ScriptBlock {Write-Host "$env:COMPUTERNAME,Remoting Successful" -ForegroundColor Green }
            { Write-Host "$computer,Error Remoting" -ForegroundColor Red }

        {Write-Host "$computer,Ping Failed" -ForegroundColor Red }


The picture above shows the result of TestPsRemoting.ps1 script.

Finally, I will use script named Install-PSonRemotePc.ps1 to install PowerShell version 3.0 on all remote machines that are labeled “Ready” in a .CSV log file.

# Filename: Install-PSonRemotePc.ps1
# Date:     May, 2013
# Author:   Alex Dujakovic
# Description: PowerShell script for installation of PowerShell ver 3.0 on remote machines
# PowerShell remoting is now working with Windows Update Standalone Installer (Wusa.exe)
# NOTE: I could use the utility PsExec.exe created by Mark Russinovich as follows:
# PsExec.exe -s \\ComputerName wusa C:\temp\Windows6.1-KB2506143-x64.msu /quiet /norestart
# ****************************************************************************************
$Credential = Get-Credential
$Hosts = Import-Csv -Path H:\TestAndLab\MyReport.csv | Where -FilterScript {$_.Status -eq 'Ready'}| Select-Object -ExpandProperty Computer
$UserName = $Credential.get_UserName()
$Password = $Credential.GetNetworkCredential().Password
$UNCPath = "\\Test-dc-01\Data\Common\Apps"
$tempFolder = "C:\Temp"
$logFile = "InstallLog.txt"
$winMgtFramework3 = "KB2506143"   # Use Expand –F:* C:\temp\Windows6.1-KB2506143-x64.msu c:\temp\KB2506143
$winMgtFrameCabFile = ""
# ****************************************************************************************
$sessions = $Hosts | New-PSSession -Authentication Credssp -Credential $Credential

Invoke-Command -Session $sessions -ScriptBlock {param($UNC, $UserName, $Password, $temp, $appUpdate, $cabFile, $logFile)

	# connecting to network share
	C:\Windows\System32\net.exe use $UNC $Password /user:$($UserName) | Out-Null
	# testing the existence of temp folder and copy file into temp folder
	if(!(Test-Path $temp)) {New-Item -Path $temp -ItemType directory | Out-Null}
	Copy-Item -LiteralPath "$UNC\$appUpdate\" -Destination $temp -Recurse -Force
	  { # start dism and add package
	    DISM.exe /Online /Add-Package /PackagePath:$temp\$appUpdate\$cabFile /Quiet /NoRestart
	  { "$($env:computername) PS ver3.0 installation with Error"| Out-File -FilePath "$UNC\$logFile" -Append -Encoding ASCII
	Remove-Item -Path "$temp\$appUpdate" -Recurse -Force

        } -ArgumentList $UNCPath, $UserName, $Password, $tempFolder, $winMgtFramework3, $winMgtFrameCabFile, $logFile

$sessions | Remove-PSSession

Please keep in mind that PowerShell remoting is not working with Windows Update Standalone Installer (Wusa.exe), so my only options are either to use a utility (PsExec.exe) created by Mark Russinovich, but that would not involve remoting, or to use Windows 7 DISM command line tool to add the latest version as package.

You will notice that in the script I’ve expanded the .msu file and passed the following .cab file “” as a package.
Note: all the above scripts and a download list file are contained in the compressed file and could be downloaded from the downloads page.

Please note: Although the author has made every reasonable attempt to achieve complete accuracy of the content, he assumes no responsibility for errors or omissions. Also, you should use this information as you see fit, and at your own risk.