Variables (PowerShell): Difference between revisions

From vwiki
Jump to navigation Jump to search
m (→‎Array Types: Update ArrayList create example)
Line 460: Line 460:


== Macros / Built-in Variables ==
== Macros / Built-in Variables ==
{|class="vwikitable"
{|class="vwikitable"
|-  
|-  
! Variable                !! Description
! Variable                !! Description
|-
|-
| <code> $_ </code>      || Either
| <code> $_ </code>      || Variable passed through pipeline from previous command
|-
|-
| <code> $? </code>      || Success/failure of previous statement - see [[#Basic_Error_Handler|Basic_Error_Handler]]
| <code> $? </code>      || Success/failure of previous statement - see [[#Basic_Error_Handler|Basic_Error_Handler]]
Line 475: Line 474:
| <code> $foreach </code> || Enumerator in a foreach loop
| <code> $foreach </code> || Enumerator in a foreach loop
|-
|-
| <code> $Host </code>    || Information about the machine being executed on
| <code> $Host </code>    || Information about the machine being executed on (can also use <code>Get-Host</code>
|-
|-
| <code> $args </code>    || Array of arguments passed to a script - see [[#Script_Arguments|Script_Arguments]]
| <code> $args </code>    || Array of arguments passed to a script - see [[#Script_Arguments|Script_Arguments]]
|}
|}
=== $env ===
This object contains all local system environment variables.  For example '''<code>$env:COMPUTERNAME</code>''' provides you with the local system's computer name.
For a full list use...
<source lang="powershell">
Get-ChildItem env:
</source>
...or for some commonly used ones...
{|class="vwikitable"
|-
! Variable        !! Description                  !! Example data
|-
| APPDATA          || Application data path        || C:\Users\RodHull\AppData\Roaming
|-
| COMPUTERNAME    || Local System's hostname      || LAPTOP-01
|-
| LOGONSERVER      || Domain Controller Logged into || DC-SERVER-03
|-
| PROCESSOR_ARCHITECTURE    || CPU Architecture    || AMD64
|-
| USERDOMAIN      || Logged in user's domain      || DOMAIN.COM
|-
| USERNAME        || Local System's hostname      || rodhull
|}


[[category:PowerShell]]
[[category:PowerShell]]

Revision as of 13:45, 7 June 2012

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 { } in order to delimit them, eg ${varname}.

PowerShell is all about manipulating objects, and all variables are objects. When you assign a value to a new variable, the type of object that it is (string, integer, etc) is automatically defined. Which is normally useful as you don't have to worry about what type of object/variable you want to create, PowerShell will work it out for you. However sometimes you need to force a variable to contain a specific data type to avoid errors or other problems down the line. Using a prefix of [type] achieves this...

 [string]$result = $PingResult.Status

Data types

Notation Data Type
[bool] True / false
[single] Single-precision 32-bit floating point number
[double] Double-precision 64-bit floating point number
[byte] 8-bit unsigned character
[int] 32-bit integer
[long] 64-bit integer
[decimal] 128-bit decimal
[char] Single character
[string] String of characters
[datetime] Date and time
[timespan] Time
[xml] XML object
[array] Array
[wmi] Windows Management Instrumentation (WMI) instance or collection
[wmiclass] WMI class
[adsi] Active Directory Services object
[Boolean] True or False value

Variable Information

As PowerShell object (variables) tend to be black boxes that can contain anything or nothing, its often necessary to understand more about one. All objects contain Properties and Methods.

  • Properties
    • Are containers for data
  • Methods
    • Are in-built functions that allow an object to be manipulated.

Variable Type

To see the object type of a variable...

PS E:\> $var.GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

Variable Properties

Simple objects such as strings will contain only one property but complicated can contain many more, and even further objects. If you pipe an object through Format-List you get a fuller picture...

PS E:\> Get-WMIObject Win32_BIOS

SMBIOSBIOSVersion : 786F3 v01.16
Manufacturer      : Hewlett-Packard
Name              : Default System BIOS
SerialNumber      : CPC123456J
Version           : HPQOEM - 20090219


PS E:\> Get-WMIObject Win32_BIOS | Format-List *

Status                : OK
Name                  : Default System BIOS
Caption               : Default System BIOS
SMBIOSPresent         : True
....TRUNCATED TO SAVE SPACE !!....
SMBIOSBIOSVersion     : 786F3 v01.16
SMBIOSMajorVersion    : 2
SMBIOSMinorVersion    : 6
SoftwareElementID     : Default System BIOS
SoftwareElementState  : 3
TargetOperatingSystem : 0
Version               : HPQOEM - 20090219
Scope                 : System.Management.ManagementScope
Path                  : \\LAPTOP1\root\cimv2:Win32_BIOS.Name="Default System BIOS",SoftwareElementID="Default System BI
                        OS",SoftwareElementState=3,TargetOperatingSystem=0,Version="HPQOEM - 20090219"
Options               : System.Management.ObjectGetOptions
ClassPath             : \\LAPTOP1\root\cimv2:Win32_BIOS
Properties            : {BiosCharacteristics, BIOSVersion, BuildNumber, Caption...}
SystemProperties      : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers            : {dynamic, Locale, provider, UUID}
Site                  :
Container             :

Variable Properties and Methods

PS E:\ > $string | get-Member

   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone()
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB)
Contains         Method                bool Contains(string value)
CopyTo           Method                System.Void CopyTo(int sourceIndex, char[] destination, int destinationIndex,...
EndsWith         Method                bool EndsWith(string value), bool EndsWith(string value, System.StringCompari...
Equals           Method                bool Equals(System.Object obj), bool Equals(string value), bool Equals(string...
GetEnumerator    Method                System.CharEnumerator GetEnumerator()
....TRUNCATED TO SAVE SPACE !!....
ToString         Method                string ToString(), string ToString(System.IFormatProvider provider)
ToUpper          Method                string ToUpper(), string ToUpper(System.Globalization.CultureInfo culture)
ToUpperInvariant Method                string ToUpperInvariant()
Trim             Method                string Trim(Params char[] trimChars), string Trim()
TrimEnd          Method                string TrimEnd(Params char[] trimChars)
TrimStart        Method                string TrimStart(Params char[] trimChars)
Chars            ParameterizedProperty char Chars(int index) {get;}
Length           Property              System.Int32 Length {get;}

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...

if (!$var) {
    "Variable is null"
}

If you expect your variable to be NULL or contain a positive number...

if ($var -gt 0) {
    "Variable is NOT null"
}

Last resort test...

if ($var.gettype().ToString() -eq "System.DBNull") {
    "Variable is null"
}

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 script to enforce using the variable that's in the main script's scope...

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

Similarly, you can create a variable within a function that has global scope...

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 ']'

...which when run produces the following output...

Created $TestVar
$TestVar already exists
$TestVar contains [ stuff ]

You can achieve the same result by using the Set-Variable CmdLet with the -Scope parameter instead of script, for example...

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 ']'

Strings

Basic manipulation tasks can be carried out by using the string object's methods, eg "string".PadRight(10) , see Get-Member -InputObject "Text" for full details.

Concatenation +

$strAB = $strA + $strB

Interpolation

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...

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

Search

To search for specific text in a string...

if (Select-String -InputObject $text -Pattern "StringToFind" -Quiet)
{
        # StringToFind found in $text
}

Match (basic)

To do a basic search/match...

if ($res.Contains("Success")) {
    # String did contain
} else {
    # Didn't
}

...which is much preferable to...

if ($res.CompareTo("Success")) {
    # Didn't match (CompareTo returns 1 if comparison fails !)
} else {
    # Did match
}

Match (extract)

To extract text that matches a regex...

$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
}

See Regular Expressions for further info on regex stuff.

Replace

Basic find and replace can be done with a string's Replace method, eg to replace "\" with "\\" in the $query variable...

 $query = $query.Replace("\", "\\")

For proper regular expressions support, use the following syntax

 $query = [regex]::Replace($query, "search", "replace")

Strip Whitespace

 $string = $string.TrimEnd()

Escape Characters

Text Description
`0 Null
`a Bell/system beep
`b Backspace
`f Form feed
`n New line
`r Carriage return
`t Tab (horizontal)
`v Vertical tab
`' '
`" "

Arrays

$array = @()            # Create blank array
$array += 34            # Add value to end of array

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

$table = @()
foreach ($instance in $set) {
    $row = "" | Select Heading1, Heading2, Heading3
    $row.Heading1 = "something"
    $row.Heading2 = "like"
    $row.Heading3 = "this"
    $table += $row
}

Add rows to an array

> $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

Select row from array

Using above array as example...

> 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

Array Types

.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 Get-Member), PS arrays generally don't have many available methods. To confirm the type that you have...

> $ArrayList.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     ArrayList                                System.Object

> $PSArray.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

To force the creation of a .NET array from a PowerShell CmdLet, create one in a fashion such as this...

$a = New-Object System.Collections.ArrayList                                        # Empty array
$a = New-Object System.Collections.ArrayList(,(Get-Content test.txt))               # Populated with contents of test.txt

Hashtables

$hash = @{}                                          # Create blank array
$hash["Name"] = "Value"                              # Add value to end of array
$hash.GetEnumerator() | Sort-Object -Property Name   # Sort hashtable

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 probably the underlying DateTime object type as this sort of problem seems to rear its head at unexpected moments when working on Windows) has a nasty habit of returning a string formatted with day and month swapped around when coming from a script.

If you do a Get-Date it all looks fine, but then you output a DateTime object in a script to some text and its wrong. Add the .ToString() method to the end at it'll sort itself, though quite why when Powershell is converting the object into a string anyway in order to display, should the object need to be explicitly told to fix the issue, seems a bit superfluous. I obviously don't understand what the underlying bug is!

If your dates are getting mixed up, it may not be your mistake, and it may be that you've fallen fowl of the problem as well.

Formatting

To control how a DateTime is displayed you can pass it through Get-Date with the -uFormat option...

Get-Date $datetime -uFormat "%R hrs, %a %d %b %Y"

Useful formatting examples...

uFormat Specifier Example
%R hrs, %a %d %b %Y 07:25 hrs, Fri 24 Dec 2010
%Y-%m-%d 2010-12-24

For the full list of formatting options see http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.aspx


Or you can use the ToString 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 (US) Formatting if things aren't behaving expected)...

$datetime.ToString("s")

Useful formatting examples...

ToString Method Specifier UK-Centric Example Output
<blank> 17/02/2011 13:07:33
d 17/02/2011
D 17 February 2011
yyyy-MM-dd HH:mm:ss 2011-02-17 13:07:33
HH:mm:ss.fff 13:07:33.423

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

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))
}

Environment

Environmental variables can be accessed via $env

$env:userprofile                  # User profile (eg C:\Users\joeblogs)
dir env:                          # Show all available variables

Macros / Built-in Variables

Variable Description
$_ Variable passed through pipeline from previous command
$? Success/failure of previous statement - see Basic_Error_Handler
$Error Last error - array of recent errors - see $error
$LastExitCode Exit code of the last natively run application
$foreach Enumerator in a foreach loop
$Host Information about the machine being executed on (can also use Get-Host
$args Array of arguments passed to a script - see Script_Arguments

$env

This object contains all local system environment variables. For example $env:COMPUTERNAME provides you with the local system's computer name.

For a full list use...

Get-ChildItem env:

...or for some commonly used ones...

Variable Description Example data
APPDATA Application data path C:\Users\RodHull\AppData\Roaming
COMPUTERNAME Local System's hostname LAPTOP-01
LOGONSERVER Domain Controller Logged into DC-SERVER-03
PROCESSOR_ARCHITECTURE CPU Architecture AMD64
USERDOMAIN Logged in user's domain DOMAIN.COM
USERNAME Local System's hostname rodhull