Difference between revisions of "Power Shell"

From vwiki
Jump to navigation Jump to search
(Added Depreciated template)
(Deleted page contents, redirect to Category)
Line 1: Line 1:
{{Depreciated|category=PowerShell}}
#REDIRECT [[:Category:Category:PowerShell]]
 
See also...
* '''[[PowerShell Examples]]''' - Example scripts
* [[CIM via PowerShell]] - Never really went anywhere, ignore
 
== Getting Started ==
=== Installation ===
Subject specific useful links are listed in the sections below, the following provide links to installers and general documentation
* [http://www.microsoft.com/windowsserver2003/technologies/management/powershell/download.mspx Windows PowerShell V1]
* [http://support.microsoft.com/kb/968929 Windows PowerShell V2]
* http://powershell.com/cs/ - Good all-round help
* http://technet.microsoft.com/en-us/library/bb978526.aspx - TechNet!
 
Whilst '''Win2008''' ships with Powershell it isn't necessarily available, to install...
# Go to '''Server Manager'''
# Go into '''Features''', then '''Add Features'''
# Tick '''Windows Powershell''', and then '''Next'''
''Normally a restart isn't required''
 
If you're running scripts from a machine that does not have (or has dodgy) internet access, you need to disable certificate checking
# In '''Internet Explorer'''
# Go to '''Internet Options''', and then '''Advanced'''
# Then under '''Security''' uncheck ''Check for publisher's certificate revocation''
 
=== Execution Policy ===
On the first run you need to allow Powershell to scripts (you need to run this command as an administrator, so if you're using Windows 7, for example, you'll need to start the Powershell console as an administrator, regardless of whether you're currently logged in as an admin).  If you have no admin rights over the PC you're using, then you'll need to select the Suspend option rather than Yes (sets just for your current session, doesn't try to write to the registry).
<source lang="powershell"> Set-ExecutionPolicy RemoteSigned </source>
It is possible to bypass the Execution Policy entirely (though you do so at your own risk, should only be used to run a script you trust where you haven't the time to fix the underlying problem, and should be reverted afterwards)...
<source lang="powershell"> Set-ExecutionPolicy Bypass </source>
 
=== Installed Version ===
To check the main installed version use the following command...
<source lang="powershell"> get-host | select version </source>
However, if you might have installed something other than the normal RTM or GA release version you'll need to the registry key <code>HKLM\Software\Microsoft\PowerShell\1</code>, which will have the following values of interest...
{|class="vwikitable"
|-
! Value                !! Data                                !! Meaning
|-
|<code> Install </code> || <code>1</code>                      || Installed (not version number)
|-
|<code> PID </code>    || <code>89383-100-0001260-04309</code> || RTM (Release to Manufacturing)
|-
|<code> PID </code>    || <code>89393-100-0001260-00301</code> || RC2 (Release Candidate 2)
|}
For more info on release version acronyms, see [http://en.wikipedia.org/wiki/Software_release_life_cycle Software Release Life Cycle]
 
=== Help Commands ===
<source lang="powershell">Get-Help <cmd>              # Provides help for CmdLets, use wildcards to broaden results.
$object | Get-Member      # Provides information about an object (variable)
$object.gettype()          # Provides variable type info (string, array, etc)</source>
 
=== Include Files ===
In order to include another Powershell script in a parent script, use a <code>.</code> and then the path to the file (there's a space between them), eg
<source lang="powershell">
. .\lib\include_file.ps1
</source>
 
=== Slow Start-Up ===
PowerShell isn't the fastest thing in the world to start, the fact that a basic PowerShell command prompt uses of 50MB of RAM gives some indication that its not lean and mean (but once up and running, and when running efficient script it can be ''very'' fast).  However if its taking minutes rather than seconds to start then there is something going wrong.
 
Normally this is due to the fact that the machine you're running from has no (or limited) internet access.  To workaround the problem you need to disable Certificate Revocation Checking...
 
# In '''Control Panel''', open '''Internet Options'''
# Go to the '''Advanced''' tab, and scroll down to the '''Security''' section
# Untick the '''>Check for publisher's certificate revocation'''
 
There is no increased security risk imposed by doing this on a machine with no/limited internet access, as the checks were previously timing-out and being bypassed anyway.
 
== Useful One-Liners ==
{|class="vwikitable"
|-
! Command              !! Description
|-
|<code> Get-Content <file-path> <nowiki>|</nowiki> Out-GridView </code> || Display (log)file in the nice Grid View window 
|-
|<code> (Get-Location -PSProvider FileSystem).ProviderPath </code>      || Current working directory 
|-
|<code> $MyInvocation.MyCommand.Name </code>                            || Current script or function name (if in a function)
|-
|<code> $env:PROCESSOR_ARCHITECTURE </code>                            || Environment architecture (32 or 64 bit), not OS architecture
|-
|<code> $host.UI.RawUI.WindowTitle </code>                              || Command shell title (change at will)
|}
 
== Variables ==
All variable names are prefixed with a $, and are case '''insensitive''' (though there's no reason to not use CamelBack notation if that floats your boat).
 
Apart from a few reserved words and characters there's little restriction on what can be used, though note that this flexibility can cause the occasional issue, whereby PowerShell gets confused as to where a variable name finishes. Variable names can be enclosed in <code> { } </code> in order to delimit them, eg <code>${varname}</code>.
 
Powershell is all about manipulating objects, and its variables are all essentially the same, not being specifically defined as an object, string, integer, etc. Which is normally useful, however sometimes you need to force a variable to contain a data type. Using a prefix of <code>[type]</code> achieves this...
<source lang="powershell"> [string]$result = $PingResult.Status </source>
 
'''Data types'''
{|class="vwikitable"
|-
! Notation                      !! Data Type
|-
| <code> [bool] </code>  || True / false
|-
| <code> [single] </code> || Single-precision 32-bit floating point number
|-
| <code> [double] </code> || Double-precision 64-bit floating point number
|-
| <code> [byte] </code> || 8-bit unsigned character
|-
| <code> [int] </code> || 32-bit integer
|-
| <code> [long] </code> || 64-bit integer
|-
| <code> [decimal] </code> || 128-bit decimal
|-
| <code> [char]  </code> || Single character
|-
| <code> [string] </code> || String of characters
|-
| <code> [datetime] </code>    || Date and time
|-
| <code> [timespan] </code>    || Time
|-
| <code> [xml] </code> || XML object
|-
| <code> [array] </code> || Array
|-
| <code> [wmi] </code> || Windows Management Instrumentation (WMI) instance or collection
|-
| <code> [wmiclass] </code> || WMI class
|-
| <code> [adsi] </code> || Active Directory Services object
|-
| <code> [Boolean] </code> || True or False value
|}
 
=== Variable Information ===
As variables tend to be black boxes that can contain anything or nothing, its often necessary to understand more about one...
 
'''Variable Type'''
<source lang="powershell">
$var.GetType()
IsPublic IsSerial Name                                    BaseType
-------- -------- ----                                    --------
True    True    Object[]                                System.Array
</source>
 
'''Variable Properties and Methods'''
<source lang="powershell">
$var | Get-Member
</source>
 
It's not uncommon for objects provided by API's to provide methods that are in fact objects themselves, you can end up with a lot of information being available once you're capable of drilling into them.
 
==== NULL ====
Checking whether a variable is NULL or not can be problematic.  In that the easiest test doesn't always work.
 
Basic test for whether a variable exists or not, works in most cases...
<source lang="powershell">
if (!$var) {
    "Variable is null"
}
</source>
 
If you expect your variable to be NULL or contain a positive number...
<source lang="powershell">
if ($var -gt 0) {
    "Variable is NOT null"
}
</source>
 
Last resort test...
<source lang="powershell">
if ($var.gettype().ToString() -eq "System.DBNull") {
    "Variable is null"
}
</source>
 
=== Scope ===
Variables only fully exist within the scope of the script or function within which they are defined.  Within functions local copies of a variable are available, but manipulating them has no effect on the real/master variable in the main scope, to get around this you can use <code>script</code> to enforce using the variable that's in the main script's scope...
<source lang="powershell">
function Local-Add($text) {
    $List += $text
}
 
function Global-Add($text) {
    $script:List += $text
}
 
$List = @()
$List += "Text message ONE"
$List.Length                          # List contains one entry
Local-Add "Text message TWO"
$List.Length                          # List still contains one entry
Global-Add "Text message THREE"
$List.Length                          # List now contains two entries
</source>
 
Similarly, you can create a variable within a function that has global scope...
<source lang="powershell">
function Create-Var {
    if (!$TestVar) {
        $script:TestVar = "stuff"
        Write-Host 'Created $TestVar'
    } else {
        Write-Host '$TestVar already exists'
    }
}
 
Create-Var
Create-Var
Write-Host '$TestVar contains [' $TestVar ']'
</source>
...which when run produces the following output...
Created $TestVar
$TestVar already exists
$TestVar contains [ stuff ]
 
You can achieve the same result by using the <code>Set-Variable</code> CmdLet with the <code>-Scope</code> parameter instead of <code>script</code>, for example...
<source lang="powershell">
function Create-Var {
    if (!$TestVar) {
        #$script:TestVar = "stuff"
        Set-Variable -Name TestVar -Value "stuff" -Scope 1
        Write-Host 'Created $TestVar'
    } else {
        Write-Host '$TestVar already exists'
    }
}
 
Create-Var
Create-Var
Write-Host '$TestVar contains [' $TestVar ']'
</source>
 
=== Strings ===
Basic manipulation tasks can be carried out by using the string object's methods, eg <code> "string".PadRight(10) </code>, see <code> Get-Member -InputObject "Text" </code> for full details.
 
'''Concatenation +'''
<source lang="powershell">
$strAB = $strA + $strB
</source>
 
<br>'''Interpolation''' <br>
Interpolation allows variables to be embedded into a string and to be resolved into their actual values.  This works between double quotes, but not between single quotes...
<source lang="powershell">
PS E:\> $sub = "replaced"
PS E:\> Write-Output "Variable has been $sub"
Variable has been replaced
PS E:\> Write-Output 'Variable has been $sub'
Variable has been $sub
</source>
 
<br>'''Search''' <br>
To search for specific text in a string...
<source lang="powershell">
if (Select-String -InputObject $text -Pattern "StringToFind" -Quiet)
{
        # StringToFind found in $text
}
</source>
 
<br>'''Match (basic)'''<br>
To do a basic search/match...
<source lang="powershell">
if ($res.Contains("Success")) {
    # String did contain
} else {
    # Didn't
}
</source>
...which is much preferable to...
<source lang="powershell">
if ($res.CompareTo("Success")) {
    # Didn't match (CompareTo returns 1 if comparison fails !)
} else {
    # Did match
}
</source>
 
<br>'''Match (extract)'''<br>
To extract text that matches a regex...
<source lang="powershell">
$res = [regex]::matches($line, "\d{4}-[A-Za-z]{3}-Week\d{1}.log")
if (-not $res.Count)
{
    # No matches found
} else {
    $res1 = $res.Item(1).Value      # 1st match to regex
}
</source>
See [[Regular Expressions]] for further info on regex stuff.
 
<br>'''Replace''' <br>
Basic find and replace can be done with a string's Replace method, eg to replace "\" with "\\" in the $query variable...
<source lang="powershell"> $query = $query.Replace("\", "\\") </source>
For proper regular expressions support, use the following syntax
<source lang="powershell"> $query = [regex]::Replace($query, "search", "replace") </source>
 
<br>'''Strip Whitespace'''<br>
<source lang="powershell"> $string = $string.TrimEnd() </source>
 
=== Escape Characters ===
{|class="vwikitable"
|-
!  Text              !! Description
|-
| <code> `0 </code>  || Null
|-
| <code> `a </code>  || Bell/system beep
|-
| <code> `b </code>  || Backspace
|-
| <code> `f </code>  || Form feed
|-
| <code> `n </code>  || New line
|-
| <code> `r </code>  || Carriage return
|-
| <code> `t </code>  || Tab (horizontal)
|-
| <code> `v </code>  || Vertical tab
|-
| <code> `' </code>  || '
|-
| <code> `" </code>  || "
|}
 
=== Arrays ===
<source lang="powershell">
$array = @()            # Create blank array
$array += 34            # Add value to end of array
</source>
 
To create an array (table) with column headings, initialise an array, then create a ''row'' variable with the column headings and add this ''row'' to the array.  This is convenient when building a table of data within a loop eg
<source lang="powershell">
$table = @()
foreach ($instance in $set) {
    $row = "" | Select Heading1, Heading2, Heading3
    $row.Heading1 = "something"
    $row.Heading2 = "like"
    $row.Heading3 = "this"
    $table += $row
}
</source>
 
'''Add rows to an array'''
<source lang="powershell">
> $array = @()
> $row = "" | Select h1,h2,h3
> $row.h1 = "esx1"
> $row.h2 = "HBA1"
> $row.h3 = "LUN1"
> $array = $array + $row
> $row = "" | Select h1,h2,h3
> $row.h1 = "esx2"
> $row.h2 = "HBA1"
> $row.h3 = "LUN2"
> $array = $array + $row
> $array
 
h1                                      h2                                      h3
--                                      --                                      --
esx1                                    HBA1                                    LUN1
esx2                                    HBA1                                    LUN2
</source>
 
'''Select row from array'''<br>
Using above array as example...
<source lang="powershell">
> if (($array |?{$_.h1 -eq "esx2"})) {"true"} else {"false"}
true
> if (($array |?{$_.h1 -eq "esx3"})) {"true"} else {"false"}
false
> $array |?{$_.h1 -eq "esx2"}
 
h1                                      h2                                      h3
--                                      --                                      --
esx2                                    HBA1                                    LUN2
 
 
> $array |?{$_.h1 -eq "esx2"} | Select -ExpandProperty h2
HBA1
</source>
 
'''Array Types'''<br>
.NET Array Lists are far more flexible than PowerShell arrays.  With ArrayLists you can easily Add and Remove members and generally enjoy much more flexibility when it comes to manipulating its contents.  Despite showing a full range of available methods (when piped through a <code>Get-Member</code>), PS arrays generally don't have many available methods.  To confirm the type that you have...
 
<source lang="powershell">
> $ArrayList.gettype()
 
IsPublic IsSerial Name                                    BaseType
-------- -------- ----                                    --------
True    True    ArrayList                                System.Object
 
> $PSArray.gettype()
 
IsPublic IsSerial Name                                    BaseType
-------- -------- ----                                    --------
True    True    Object[]                                System.Array
</source>
 
To force the creation of a .NET array from a PowerShell CmdLet, create one in a fashion such as this...
<source lang="powershell">
$a = New-Object System.Collections.ArrayList(,(Get-Content test.txt))
</source>
 
=== Hashtables ===
<source lang="powershell">
$hash = @{}                                          # Create blank array
$hash["Name"] = "Value"                              # Add value to end of array
$hash.GetEnumerator() | Sort-Object -Property Name  # Sort hashtable
</source>
 
=== Datetime ===
The object structure: http://msdn.microsoft.com/en-us/library/system.datetime.aspx
 
==== Improper (US) Formatting ====
Be aware that if you live somewhere dates are normally formatted properly (eg not the USA), then Powershell (or the underlying DateTime object type) has a nasty habit of returning a string formatted with day and month swapped around when coming from a script.
 
If you do a <code> Get-Date </code> it all looks fine, but then you output a DateTime object in a script to some text and its wrong.  Add the <code> .ToString() </code> method to the end at it'll sort itself, though quite why when Powershell is converting the object into a string anyway, the object needs to be explicitly told to fix the issue, seems a bit superfluous.
 
==== Formatting ====
To control how a DateTime is displayed you can pass it through <code> Get-Date </code> with the '''<code> -uFormat </code>''' option...
<source lang="powershell">Get-Date $datetime -uFormat "%R hrs, %a %d %b %Y"</source>
 
Useful formatting examples...
{|class="vwikitable"
|-
! uFormat Specifier                            !! Example
|-
| <code> %R hrs, %a %d %b %Y </code>    || <code> 07:25 hrs, Fri 24 Dec 2010 </code>
|-
| <code> %Y-%m-%d </code>              || <code> 2010-12-24 </code>
|}
For the full list of formatting options see http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.aspx
 
 
Or you can use the '''<code>ToString</code>''' method provided by the object, to convert the date.  If you use a generic specifier you should get a local format output (though see note above about [[#Improper_.28US.29_Formatting|Improper (US) Formatting]] if things aren't behaving expected)...
<source lang="powershell">$datetime.ToString("s")</source>
 
Useful formatting examples...
{|class="vwikitable"
|-
! ToString Method Specifier          !! UK-Centric Example Output
|-
| <code> <blank> </code>            || <code> 17/02/2011 13:07:33 </code>
|-
| <code> d </code>                  || <code> 17/02/2011 </code>
|-
| <code> D </code>                  || <code> 17 February 2011 </code>
|-
| <code> yyyy-MM-dd HH:mm:ss </code> || <code> 2011-02-17 13:07:33 </code>
|-
| <code> HH:mm:ss.fff </code>        || <code> 13:07:33.423 </code>
|}
For the full list of formatting options see http://technet.microsoft.com/en-us/library/ee692801.aspx, and even more detail at http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.aspx
 
==== Converters ====
<source lang="powershell">
function ConvertLocalToUnix([datetime]$datetime)
{
    ($datetime.ToUniversalTime() - ([datetime]'1/1/1970 00:00:00')).TotalSeconds
}
 
function ConvertUnixtoLocal($sincepoch)
{
    [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($sincepoch))
}
</source>
 
=== Environment ===
Environmental variables can be accessed via <code> $env </code>
 
<source lang="powershell">
$env:userprofile                  # User profile (eg C:\Users\joeblogs)
dir env:                          # Show all available variables
</source>
 
=== Macros / Built-in Variables ===
 
{|class="vwikitable"
|-
! Variable                !! Description
|-
| <code> $_ </code>      || Either
|-
| <code> $? </code>      || Success/failure of previous statement - see [[#Basic_Error_Handler|Basic_Error_Handler]]
|-
| <code> $Error </code>  || Last error - array of recent errors - see [[#$error|$error]]
|-
| <code> $LastExitCode </code> || Exit code of the last natively run application
|-
| <code> $foreach </code> || Enumerator in a foreach loop
|-
| <code> $Host </code>    || Information about the machine being executed on
|-
| <code> $args </code>    || Array of arguments passed to a script - see [[#Script_Arguments|Script_Arguments]]
|}
 
=== Script Arguments ===
In order to be able to parameters to a script the <code>$args</code> variable can be used.  Each argument is provided within the array in order, eg
 
<source lang="powershell">
# 1st param is send yes or no
# 2nd param is email address
 
if ($args[0]) {
    Write-Host "Email sending enabled...!"
    Write-Host "Will send to" $args[1]
} else {
    Write-Host "Email sending disabled."
}
</source>
 
So for example...
<pre>
[PS > ./params.ps1 0
Email sending disabled.
[PS > ./params.ps1 1 test@abc.com
Email sending enabled...!
Will send to test@abc.com
</pre>
 
== Conditional Operators ==
== Comparison ==
{|class="vwikitable"
|-
!  Operator          !! Description
|-
| <code> -eq </code>  || Equal to (implied case insensitive)
|-
| <code> -ieq </code> || Equal to (case insensitive)
|-
| <code> -ceq </code> || Equal to (case sensitive)
|-
| <code> -lt </code>  || Less than
|-
| <code> -gt </code>  || Greater than
|-
| <code> -ge </code>  || Greater than or Eqaul to
|-
| <code> -le </code>  || Less than or equal to
|-
| <code> -ne </code>  || Not equal to
|-
| <code> -match </code>  || Match (ie string contains) anywhere within string (can be regex)
|-
| <code> -notmatch </code>  || Does not match (ie string contains) (can be regex)
|-
| <code> -like </code>  || Like (ie string is), stricter than match (can be regex)
|-
| <code> -notlike </code>  || Not like (ie string is not) (can be regex)
|}
 
=== Logic ===
{|class="vwikitable"
|-
!  Operator            !! Description
|-
| <code> -not </code>  || Not
|-
| <code> ! </code>    || Not
|-
| <code> -and </code>  || And
|-
| <code> -or </code>  || Or
|}
 
== Credentials ==
=== Get-Credential ===
When running commands that require a connection to a remote machine its useful to be able to store a user/pass combination so that you aren't repeatedly prompted every time you run a command.  Create a credential object, then supply that in place of a username in a command
 
<source lang="powershell">
PS H:\> $cred = Get-Credential
 
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
PS H:\> Get-WMIObject -query "SELECT * FROM Win32_OperatingSystem" -credential $cred -computer 159.104.224.167
 
SystemDirectory : C:\WINDOWS\system32
Organization    : TF
BuildNumber    : 3790
RegisteredUser  : TF
SerialNumber    : 69712-640-3560061-45009
Version        : 5.2.3790
</source>
 
However, this doesn't really help much in a fully scripted situation where you need to supply user and pass in an unattended fashion, for that you also need the help of <code> ConvertTo-SecureString </code>, but if you want to be secure you need to use [[Power_Shell#Store Password Securely|Store Password Securely]]
 
=== ConvertTo-SecureString ===
* http://technet.microsoft.com/en-us/library/dd819512.aspx
* Converts encrypted standard strings to secure strings
The following example creates a Credential object that can be used for in place of <code> Get_Credential </code>
<source lang="powershell">
$pass = ConvertTo-SecureString $svr.pass -asplaintext -force
$cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist $svr.user,$pass
$wmiobj = Get-WMIObject -query "SELECT * FROM Win32_BIOS" -credential $cred -computer $svr.ip
</source>
 
=== Store Password Securely ===
Adapted (a little) from http://bsonposh.com/archives/338 by Brandon
This is a two stage process, 1st you have to create a file with your (encrypted) password in (its encrypted by the currently logged in user - so if its going to be used in a scheduled task, make sure the user that will execute the script creates the password file).
<source lang="powershell">
$Credential = Get-Credential
$credential.Password | ConvertFrom-SecureString | Set-Content "Pass.fil"
</source>
 
Then you can use this in a script, the <code> $cred </code> is a standard credential object.
<source lang="powershell">
$pass = Get-Content "Cred.fil" | ConvertTo-SecureString
$cred = New-Object System.Management.Automation.PsCredential("DOMAIN\user",$pass)
</source>
 
For a complete, but simple user/pass caching system use something like the following...
<source lang="powershell">
# Check for credential files, create if required
if (!(Test-Path $UserFile) -or !(Test-Path $PassFile)) {
    Write-Host "Credential files not found"
    $cred = Get-Credential -Credential ($env:userdomain + "\" + $env:username)
    $cred.UserName | Set-Content $UserFile -Force
    $cred.Password | ConvertFrom-SecureString | Set-Content $PassFile -Force
    Write-Host "Credentials saved"
}
   
# Load password credential from encrypted file
$pass = Get-Content $PassFile | ConvertTo-SecureString
$user = Get-Content $UserFile
$cred = New-Object System.Management.Automation.PsCredential($user, $pass)
</source>
...obviously to make the above more useful you'd test that the user/pass combo supplied was correct prior to saving to file.
 
=== Logged-In User's Credentials ===
To use the credentials of the existing logged in session where a CmdLet requires that you specify them use the following...
<source lang="powershell">
$cred = [System.Net.CredentialCache]::DefaultCredentials
</source>
 
== Logging ==
The easiest way to setup logging from with a script is to use the Transcript functionality which will log all output to a transcript file.  Note that <code> Write-Host </code> only places line feeds at the end of lines, not carriage returns, therefore Notepad will display such output as one long line.  You'll need to use an editor that can handle lines only terminated by LF's (WordPad if you can't install anything, otherwise get something better, eg http://www.scintilla.org/SciTE.html).
 
<source lang="powershell">
Start-Transcript -Path C:\Users\name\Scripts\script.log -Append -NoClobber
# script
Stop-Transcript
</source>
 
== External Processes ==
One of PowerShell's greatest failings is its inability to run external commands with any predictability, severely limiting tits scope and often forcing you to run small PowerShell scripts from other scripts rather than having one all-in-control PS script. The following example demonstrates how to run any cmd line...
<source lang="powershell">
$cmd = "rrdtool.exe update $rrd $updates"
$proc_res = &$executioncontext.InvokeCommand.NewScriptBlock($cmd)
</source>
 
 
== WMI ==
Cmdlet for using WMI via PowerShell is '''Get-WMIObject'''.  By default, it retrieves objects from the CIMv2 namespace for example...
 
<source lang="powershell">
PS H:\> Get-WMIObject -query "Select * from Win32_OperatingSystem"
 
SystemDirectory : C:\WINDOWS\system32
Organization    :
BuildNumber    : 2600
RegisteredUser  : Somebody
SerialNumber    : 76487-OEM-0011903-00102
Version        : 5.1.2600
</source>
 
Further examples...
<source lang="powershell">
# Get OS CPU info (address width, speed, FSB etc
$cpu = Get-WMIObject -query "SELECT * FROM Win32_Processor WHERE DeviceID='CPU0'" -credential $cred -computer $svr
 
# Get local disks
$drives = Get-WMIObject Win32_LogicalDisk -filter "Description = 'Local Fixed Disk'" -credential $cred -computer $svr
 
# Get Local Security Policy user right (namespace needs to be set as required WMI object not in default CIMV2)
Get-WmiObject -Class RSOP_UserPrivilegeRight -Namespace "ROOT\RSOP\Computer" -Filter "UserRight= 'SeLockMemoryPrivilege'"
</source>
 
 
=== Find Classes and Properties ===
 
In order to find the correct class use...
<source lang="powershell">
Get-WMIObject -list -credential $cred -computer 159.104.224.167 | Select-String -InputObject {$_.Name} Win32*
</source>
 
To then see all the properties of a class use (if this doesn't work on remote machines (access denied) - it may be due to a known bug in Power Shell v1 whereby <code>Get-WMIObject</code> can't impersonate (or you may just have the wrong credentials)...
<source lang="powershell">
Get-WMIObject Win32_BIOS | Format-List *
</source>
 
[http://technet.microsoft.com/en-gb/magazine/2009.02.windowspowershell.aspx TechNet article: Windows PowerShell Best Inventory Tool Ever!]
 
In general its easiest to browse what's available to locatye what want by using the excellent [http://thepowershellguy.com/blogs/posh/archive/2007/03/22/powershell-wmi-explorer-part-1.aspx PowerShell WMI Explorer]
 
== Network ==
=== Ping ===
<pre>
PS H:\> $objPing = New-Object system.Net.NetworkInformation.Ping
PS H:\> $objPing.Send('127.0.0.1')
 
Status        : Success
Address      : 127.0.0.1
RoundtripTime : 0
Options      : System.Net.NetworkInformation.PingOptions
Buffer        : {97, 98, 99, 100...}
</pre>
 
=== Name/Address Resolution ===
'''IP to Name'''
* Be aware, where no name can be found, the call throws an exception.  If assigning result to a variable, then it seems to return the local hostname, which is odd.
<pre>
PS H:\> [System.Net.Dns]::GetHostbyAddress("159.104.31.83")
 
HostName                                Aliases                                AddressList
--------                                -------                                -----------
L-STRUTTS1                              {}                                      {159.104.31.83}
</pre>
 
'''Name to IP'''
<pre>
PS H:\> [System.Net.Dns]::GetHostAddresses("l-strutts1")
 
Address          : 1394567327
AddressFamily    : InterNetwork
ScopeId          :
IsIPv6Multicast  : False
IsIPv6LinkLocal  : False
IsIPv6SiteLocal  : False
IPAddressToString : 159.104.31.83
</pre>
 
=== WoL Script ===
This script is largely based on stuff from http://thepowershellguy.com/blogs/posh/archive/2007/04/01/powershell-wake-on-lan-script.aspx
<source lang="powershell">
script-file version
param ([String]$macString = $(throw 'mac address is required'))
$mac = $macString.split(':') | %{ [byte]('0x' + $_) }
 
if ($mac.Length -ne 6)
{
  throw 'mac address must be 6 hex numbers separated by :'
}
 
$UDPclient = new-Object System.Net.Sockets.UdpClient
$UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)
$packet = [byte[]](,0xFF * 6)
$packet += $mac * 16
write-debug ([bitconverter]::tostring($packet))
 
[void] $UDPclient.Send($packet, $packet.Length)
[void] $UDPclient.Close()
write-debug "Wake-On-Lan magic packet of length $($packet.Length) sent to $macString"
</source>
 
 
 
== Exceptions and Error Handling ==
* http://huddledmasses.org/trap-exception-in-powershell/ - Exception trapping
* http://www.pluralsight.com/community/blogs/keith/archive/2007/01/22/45814.aspx - Error handling
 
To control how a script behaves as a result of an exception, modify the <code> $ErrorActionPreference </code> variable, if required.  '''In the default continue mode, an error will output to the screen, then the script will continue.  By outputting the error to the screen PowerShell CmdLets consider the exception to have been handled!  If you want the opportunity to <code>catch</code> or <code>trap</code> you must append <code>-ErrorAction:Stop</code> to the CmdLet you expect might fail.'''
 
{|class="vwikitable"
|-
! Value            !! Effect
|-
| Continue          || [Default] Outputs error, but keeps processing
|-
| SilentlyContinue  || No output and it keeps going
|-
| Inquire          || Prompt user for action
|-
| Stop              || Outputs error and halts processing
|}
 
=== $error ===
Provides a list of recent errors experienced - which can be invaluable for properly identifying and investigating errors.  The object is the same as found in a pipeline when an exception has occurred.
 
{|class="vwikitable"
|-
! Property                                    !! Description                              !! Example (VC login error)
|-
| <code>$error[1].Exception.GetType().FullName</code> || Error class for specific error || <code>[VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.InvalidLogin]</code>
|-
| <code> $error[1].Exception.Message </code>  || User friendly error message  || <code>16/11/2010 10:32:52  Connect-VIServer  Login failed due to a bad username or password.</code>
|-
| <code> $error[1].ErrorDetails.Message </code> || Shorter friendly error message  || <code> Login failed due to a bad username or password </code>
|-
| <code> $error[1].CategoryInfo.Reason </code> || Short error message        || <code> InvalidLogin </code>
|-
| <code> $error[1].InvocationInfo </code>    || Invocation info (what triggered exception) || Various info including script command
|}
 
=== Basic Error Handler ===
If you know where the error is likely to occur, then just place an error catcher immediately after it.  This doesn't stop the exception appearing on the console, but does allow you to take some action as a result.
<source lang="powershell">
if (-not $?) {
    # Handle error here
  }
</source>
 
=== Try...Catch ===
Used to catch an exception in a script block where an exception may be likely.  Stops the exception being shown on the console and gives you a chance to do something about it (as long as you've set <code>$ErrorActionPreference</code> to Stop or used <code>-ErrorAction:Stop</code>).
<source lang="powershell">
try
{
    # Something in which an exception is likely
}
catch
{
    Write-Host "FAILED: $_"
    Exit
}
</source>
 
The error returned by the CmdLet can be found in <code> $_ </code>, so this can be tested to ensure the error is as expected (just because you though a command might fail, doesn't mean it failed in the way you expected).  For example, <code> $_ </code> will contain the bold bit of the following error.
Get-Log : '''Cannot validate argument on parameter 'StartLineNum'. The 0 argument is less than the minimum allowed range of 1. Supply an argument that is greater than 1 and then try the command again.'''
At C:\Users\simonstrutt\Documents\Scripts\ESX-LogTail.ps1:20 char:57
+    $ESXLog = Get-Log $logKey -VMHost $ESX -StartLineNum <<<<  $LineNo
    + CategoryInfo          : InvalidData: (:) [Get-Log], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.Commands.GetLog
 
This can be elaborated on to make the catch handling more specific, by making the catch block executed depend on the error class.  The error class can be determined by making causing the error to be thrown, in which case the class can be found at <code>  $_.Exception.GetType().FullName </code>.
<source lang="powershell">
try {
  $proxy = New-WebServiceProxy -uri $endpoint -cred $credential -ErrorAction:Stop
} catch [System.Net.WebException] {
    Write-Host "ERROR: Unable to connect to SOAP server"
    Write-Host $_
} catch {
    Write-Host "ERROR: Unexpected error"
    Write-Host $_
    Write-Host $_.Exception.Message
    Write-Host $_.Exception.GetType().FullName
}
</source>
 
=== Trap ===
Used to capture any unhanded exception that occurs anywhere.  I tend to consider this a last resort catch-all, though really it depends on the nature of your script.  For example if your script is reliant on a connection to a server that can go down, you can design a trap to recover from that specific occurrence rather than having to put a Try...Catch around every operation that could fail.
 
The key to an effective <code>trap</code> is allowing for the fact that ''anything'' might go wrong, therefore you have to set your traps up to handle only specific cases and in all likelihood stop on anything else.
 
<source lang="powershell">
trap {
    # Handle the exception
    Continue
}
</source>
 
== Background Jobs ==
This feature allows Cmdlets to run as background jobs.  Its fairly easy to work out how to make individual Cmdlet's or PowerShell scripts to run as jobs (see [http://technet.microsoft.com/en-us/library/dd315273.aspx about_Jobs], but running functions are a bit more of a pain as the job runs in a new scope (so any functions, variables etc that are defined in your scripts scope, have no meaning in the background job's scope).  Therefore functions, etc, have to explicitly included as a script block in the background job...
 
<source lang="powershell">
# First define the script block and function
$funky = {
    function TestJob {
        $processList = Get-Process
        Return $processList                          # Yes could be achieved in one line, but wouldn't be much of a function!
    }
}
 
# Start job
$job = Start-Job -ScriptBlock {TestJob} -InitializationScript $funky
 
$job | Format-List *                                # Displays created $job object
 
Wait-Job -job $job                                  # Wait on completion of job
Receive-Job -job $job                                # Gets result of $job (ie result of Get-Process)
Get-Job                                              # Shows list of jobs (current and completed)
</source>
 
Arguments have to be passed through to the job through the <code> -InputObject </code> parameter, which isn't particularly pretty.  For further info see http://robertrobelo.wordpress.com/2010/03/14/background-jobs-input-gotcha/ for a decent explanation, though I do kind of cover this below.
 
'''Script Block''' or '''Script File'''...?
Your background task can either take the form of a script block, or a script file.  Personally I prefer to keep everything in one script as it makes organisation easier, up to an extent.  There is a limit to size of a script block, no idea what it is, as the script I as trying to mangle into running as jobs was large (20 KB, nearly 1000 lines) and I didn't have a convenient way to test.  But your jobs will fail if they're too big.
 
=== Job Control ===
Below is a fuller example of using background jobs to manage multiple work streams
 
Further reading...
* http://ryan.witschger.net/?p=22 - Multi-Threading in PowerShell V2
 
<source lang="powershell">
$funky = {
    function TestJob {
        $var = $Input.‘<>4__this’.Read()
        Write-Host "This is job " $var[0]
        Start-Sleep $var[1]
    }
}
 
$jobs = @()
$job = "" | Select Name, Vars, State, Obj
$job.Name = "Job1"
$job.Vars = ($job.Name, 10)
$jobs += $job
$job = "" | Select Name, Vars, State, Obj
$job.Name = "Job2"
$job.Vars = ($job.Name, 5)
$jobs += $job
 
foreach ($job in $jobs) {
    Write-Host ("Starting " + $job.Name)
    $job.Obj = Start-Job -ScriptBlock {TestJob} -InitializationScript $funky -Name $job.Name -InputObject $job.Vars
    $job.State = "Running"
}
 
# Idle loop
While (1) {
    $JobsRunning = 0
    foreach ($job in $jobs) {
        if ($job.State -ne $job.Obj.JobStateInfo.state) {
            Write-Host ($job.Name + " state now " + $job.Obj.JobStateInfo.state)
            $job.State = $job.Obj.JobStateInfo.state
        }
        if ($job.State -eq "Running") {
            $JobsRunning += 1
        }
  }
  Write-Host ("$JobsRunning of " + $jobs.count + " jobs still running")
  if ($JobsRunning -eq 0) {
      Break
  }
  Start-Sleep 1
}
   
Write-Host "All finished...!"
 
# To see output from jobs
# Get-Jobs              - shows list of jobs
# Receive-Job -Id x    - shows the data returned from the job
</source>
 
=== Transcripts / Logging ===
I haven't been able to get transcripts working properly with background jobs, at least, not to my liking.  It is possible to capture the console output from a background job into the transcript of the parent or master script.  But if you're running a large background job script and want to capture the transcript/logging from job separately you have to faff around - you can't just start and stop transcribing from within the (child) background job script, it won't write anything to disk.
 
Similarly, you can't redirect the output from <code> Receive-Job </code> to a file, you'll lose some of the output (I think this may only capture StdErr and/or explicitly returned objects, standard <code> Write-Host </code> output is dropped).
 
One way around this is to stop the transcript for your master/parent script, then start a temporary trasncript to capture then return from your child job once its finished, so so something like...
<source lang="powershell">
if ($job.State.ToString() -eq "Completed") {
    Write-Host ($job.Name + " writing log to job-" + $job.Name + ".log")
    # Nasty logging handling (Receive-Job StdOut to console only, can't redirect to file, can only catch StdErr to file)
    Stop-Transcript
    Start-Transcript -Path ("job-" + $job.Name + ".log")
    Receive-Job -Id $job.job.Id
    Stop-Transcript
    Start-Transcript -Path $Logfile -Append
}
</source>
 
 
=== Gotchas ===
* '''Working Directory'''
** The background job script runs in a new context, therefore it runs in the default path.  If you tend to run your scripts from a non-default path, and need to read/write files, get the current directory using <code>Get-Location</code> and pass it to your job as a parameter, then use <code>Set-Location</code> in your job.
* '''VMware PowerCLI 64 bit'''
** Due to a bug you can't launch background jobs against VMware vSphere (VI4) hosts in a 64-bit environment, PowerShell will crash.  Its probably a bug in PowerCLI, and will hopefully be fixed in a future release (bug exists in v4.1u1 and possibly other versions).  You can run scripts in 32-bit (see [[#Useful_One-Liners|Useful One-Liners]] on how to detect in your script).

Revision as of 15:37, 4 October 2016