Applications developed using the .NET Framework aren't compiled in the true sense of the word. Writing an application in Visual Basic .NET, Visual C++ .NET or Visual C# .NET using Visual Studio doesn't result in machine code being generated. Instead, the result is Microsoft Intermediate Language (MSIL) code - a platform independent "virtual" machine code, which runs on top of the .NET Framework Common Language Runtime (CLR).
The .NET Framework exposes numerous data types, including simple types, such as Boolean, Char, Int32 and Int64 as well as hundreds of compound data types, such as System.Array, System.Buffer and System.Tuple.
When compiling a Visual Studio application (into MSIL), the compiler performs rigorous type checking, meaning that the resultant code will be "type safe" at the point of execution.
As already discussed in the , PowerShell sits directly on top of the .NET Framework, and is therefore able to use any of the .NET data types. However, in contrast to applications written using Visual Studio, PowerShell is an interpreted language, meaning that the MSIL code is generated on-the-fly, at run-time. Therefore, type checking cannot be carried out with the same level of rigor, resulting in code that may not be type safe.
PowerShell has been designed to achieve two conflicting goals. One the one hand, it needs to provide an interactive shell which can quickly return "rich" results to both novices and experts alike. On the other hand, it needs to provide a robust scripting environment for configuring, controlling and monitoring enterprise class systems, such as Microsoft Active Directory, Microsoft Azure, Microsoft Exchange, Microsoft Hyper-V, Microsoft SQL Server and Microsoft System Center.
In order to achieve the first of these goals, PowerShell allows both dynamic typing and loose typing. This doesn't cause concern when using PowerShell as an interactive shell, but it's not good news for production, server-side scripts.
As mentioned above, PowerShell allows dynamic typing to be used. That is, a variable can assume a data type at run-time, and subsequently change type during execution. When assigning a value to a variable, without specifying a data type, PowerShell will try to select the most appropriate data type for you. If you then perform an operation that requires a type change, PowerShell will oblige. Consider the following example:
As you can see, the variable $i starts off as an Int32 (a 32-bit signed integer), but then becomes a Double when its value exceeds the capabilities of an Int32. It's tempting to think that this behaviour is desirable or even convenient. However, automatic type selection and dynamic type changes can quickly lead to unpredictable results, which isn't good for server-side production scripts, where data type overflow or mismatches must be caught, logged and possibly alerted.
A far more robust approach is to assign static data types to variables. This means that issues such as integer overflows will raise exceptions, but at least the script will maintain values that are within bounds and can therefore be safely passed to functions and class methods that expect the given data type. To assign a static data type to a variable, simply specify the data type name in square brackets, for example:
As you can see, the integer overflow caused an to be raised, however, this can be caught using an exception handler (covered later on).
PowerShell is also loosely typed. That is, you can perform operations such as add a string value to an integer value, or add an integer value to a string value. Again, PowerShell will try to give you the result you desire. Consider the following examples:
This behaviour is known as data type coercion. And again, although convenient, this too can cause unpredictable results, especially if the values being coerced have been sourced from human input or from a data feed of some kind.
Again, a more robust approach is possible. In this case, it's type casting, where the developer uses a casting method (see , below) to specify exactly what data type(s) are expected at the point of carrying out the data operation. For example, you may have read a numeric text string from a data file and need to add the value to a running total. In this instance, the running total will be a known integer data type and the data being loaded from the file will be a string value. Using a casting method will cause an to be raised if the value can't be represented as an integer. This can be seen below:
However, with valid numeric input data, the casting operation succeeds:
In addition to static data type assignment and type casting, PowerShell's strict mode can also be used to further harden server-side scripts. Strict mode is enabled using the Set-StrictMode cmdlet. There are currently two versions of strict mode, unsurprisingly, version one and version two. Enabling strict mode version two enforces two important features:
This capability is similar to Perl's use strict; and Python's use strict "vars"; directives. An example of PowerShell's behaviour before and after strict mode has been enabled can be seen below:
Once again, this behaviour can lead to unpredictable results, for example, you could unknowingly continue to reference a class property that has been made obselete. However, with strict mode enabled, an is raised:
The .NET Framework (and therefore PowerShell) provides the following scalar (single value) data types:
Name | Alternative Name | Description | Minimum Value | Maximum Value |
---|---|---|---|---|
String | ASCII or unicode string. Strings can be encapsulated in single or double quotes, with single quotes being the preferred option. Double-quotes have three uses:
|
An empty string, i.e.: '' | 2,147,483,647 bytes | |
Char | Unicode 16-bit character. | [Char] 0x0000 | [Char] 0xffff | |
Byte | An 8-bit unsigned character. | 0 | 255 | |
Int32 | Int | 32-bit signed integer. | -2,147,483,648 | 2,147,483,647 |
Int64 | Long | 64-bit signed integer. | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
Boolean | Bool | Boolean true or false. | $false (or zero) | $true (or non-zero) |
Decimal | A 128-bit decimal value. | -79,228,162,514,264,337,593,543,950,335 | 79,228,162,514,264,337,593,543,950,335 | |
Single | Float | Single-precision 32-bit floating point number. | -3.402823 x 1038 | 3.402823 x 1038 |
Double | Double-precision 64-bit floating point number. | -1.79769313486232 x 10308 | 1.79769313486232 x 10308 | |
DateTime | Used to store and manipulate dates. | 1st January 0001 @ 00:00:00hrs | 31st December 9999 @ 23:59:59 |
PowerShell arrays are based upon the .NET Framework System.Array data type. This data type is capable of representing single and multi-dimensional arrays, with the arrays being able to contain identical or varying data types.
The following examples demonstrate simple single-dimension array operations:
Multi-dimensional arrays are defined and manipulated as follows:
Like most scripting languages, PowerShell supports hash tables (also known as associative arrays or dictionary objects). A hash table lets you associate a given key value with a given data value. Data can then be retrieved from the hash table using the key values.
The following example demonstrates the creation of a simple hash table:
Values can then be retrieved using their key values, for example:
If the hash table doesn't contain a data value for the given key, the returned value will be $null.
To add a value to a hash table, or amend an existing value, simply do the following:
To remove a value from a hash table, use the Remove method:
PowerShell's behaviour, once again, comes into play when dealing with hash tables. However, you can quite easily enforce static data types for your hash values, for example:
The Count property can be used to return the number of entries in a hash table:
Use the ContainsKey() and ContainsValue() methods to determine if the given key or value exists in the hash table:
To return the entire list of keys as an array, or the entire list of data values as an array, use the Keys or Values properties:
Hash tables cannot be natively sorted. However, we can use the GetEnumerator() method to cause the hash to be "pipelined". This makes it possible to invoke the cmdlet, for example:
Also, you can use the Keys property to programmatically step through the contents of a hash, for example:
To clear a hash table completely, use the Clear method:
Finally, you can use a hash table to "splat" parameters into a function or cmdlet, for example:
As mentioned in section , PowerShell natively supports XML (eXtensible Markup Language). XML encoded data can be manipulated directly using the XML data type. In addition, the ConvertTo-Xml cmdlet can be used to generate XML output and the Select-Xml cmdlet can be used to search for values within XML data.
PowerShell uses the well-known concept of scopes to protect variables, functions and other language components from inadvertent modification. When defining a variable, the developer can specify a scope to control the visibility of the variable throughout the script. There are four scopes: Global, Script, Local and Private.
The Global scope is created when the PowerShell process starts. Any variables created at the interactive shell prompt that aren't created as Private automatically belong to the Global scope. Any scripts that execute within the shell can subsequently access these variables, as long as they specify the Global scope in the variable reference, for example: $Global:foo.
Once a script is executing, any variables created in the script body, that is, not in a function or procedure, automatically belong to the Script scope, unless the developer chooses to make them Private or Global.
The Local scope behaves differently. When at the interactive shell prompt, the Local scope and the Global scope are synonymous. When in the body of a script, the Local scope and the Script scope are synonymous. However, when in a function or procedure, the Local scope is its own entity, that is, a function or procedure can access the Global, Script and Local scopes.
Assigning a variable to the Private scope prevents it from being visible to child scopes, for example, a variable created as Private in the script body will not be visible from within a function.
Default Scope | : | Global |
---|---|---|
Visible Scopes | : | Global (Local = Global) |
Default Scope | : | Script |
---|---|---|
Visible Scopes | : | Global & Script (Local = Script) |
Default Scope | : | Local |
---|---|---|
Visible Scopes | : | Global, Script & Local |
It is strongly recommended that you do not reference Global or Script variables directly from within a function or procedure. Doing so prevents, or at least complicates, code re-use. A better approach is to pass the the Global or Script values, or pointer references thereof, to the function or procedure as parameterised input. The exception to this rule is when referencing PowerShell's own Global variables, such a $PID, which holds the process ID of the PowerShell process.
When a PowerShell script is executed, any functions, procedures or variables defined in the Global scope will persist in the Powershell process when the script exits. For example, the following script defines two functions, one in the global scope, and one with no scope definition (it will assume the Script scope - see above).
If we execute the script, the function globalFunc() will be available from the shell when the script exits. However, the function scriptFunc() won't, for example:
However, there are times when it would be convenient for all functions, procedures and variables defined in a given script to persist when the script exits. This can be achieved using Dot Sourcing, where we prefix the script name with a dot and a space, for example:
Dot sourcing is particularly useful if you have a script "library" of useful administrative functions that aren't in a ; you can simply invoke the script using dot sourcing to make its functions available in your current shell.
PowerShell automatically creates a number of variables, which it uses to store various items of state data, some of which are detailed below:
Variable | Description |
---|---|
$$ | Holds the last command in the shell history; equivalent to (Get-History)[-1]. |
$? | Flag indicating whether the previous command succeeded (see ). |
$_ or $PSItem | Holds the current object in the pipeline (similar to Perl's default variable). |
$args | Array containing the arguments passed to the script. |
$Error | Array containing the shell's error history. Can be cleared by calling $Error.Clear(). |
$ForEach | Enumerator for loops. |
$HOME | Contains the current user's Windows profile path (e.g.: C:\USERS\JOHNDOE). File system cmdlets accept the tilde character (~) as shorthand. |
$Host | An object that represents the hosting application of the PowerShell process. Useful for performing raw user-interface operations, for example, $host.UI.PromptForCredential('Dialogue Title', 'Prompt', 'username', '' );. |
$input | Contains all of the data passed to a cmdlet via the pipeline. Introduced in PowerShell V3.0. |
$LASTEXITCODE | Contains the return code from the last executed Windows program. |
$Matches | Contains the results of the last comparison. |
$MyInvocation | Contains information about the script or function being executed. |
$PID | The process ID of the current PowerShell process. |
$PROFILE | Holds the path of the current PowerShell profile script. |
$PSBoundParameters | A System.Management.Automation.PSBoundParametersDictionary object that stores all parameters passed to the script or current function via the param() construct. The variable behaves like a regular . |
$PSCulture | Holds the current system culture, for example, en-GB. The system culture determines how dates and currencies, Etc., are displayed. |
$PSCommandPath | Holds the full path of the current PowerShell script. See also $PSScriptRoot. Introduced in PowerShell V3.0; the PowerShell V2.0 equivalent is $MyInvocation.MyCommand.Path; |
$PSHOME | Contains the path of the PowerShell installation. |
$PSScriptRoot | Holds the directory (folder) of the current PowerShell script. Introduced in PowerShell V3.0; the PowerShell V2.0 equivalent is Split-Path -Path $MyInvocation.MyCommand.Path -Parent; |
$PSUICulture | Holds the current system user-interface (UI) culture, for example, en-US. The UI culture determines the language that is used to display titles, prompts and messages. |
$PSVersionTable | Holds the PowerShell version, .NET Framework Common Language Runtime (CLR) version, Etc. |
$PWD | Holds the fully qualified path of the current directory. |
In addition, PowerShell also provides the following constants:
Constant | Description |
---|---|
$false | Represents boolean false (zero). |
$null | An uninitialised, undefined or meaningless value. Returned by some functions to indicate an error or no data. Also commonly assigned to a variable before instantiation (e.g.: before creating an instance of an object class). |
$true | Represents boolean true (non-zero). |
[Math]::E | Euler's number. Used in continuous growth calculations, such as when calculating compound interest or modelling biological processes. |
[Math]::PI | Pi - the ratio of a circle's circumference to its diameter. |
KB, MB, GB, TB and PB | Built-in numeric representations of kilobyte, megabyte, gigabyte, terrabyte and petabyte. For example, 1PB = 1,125,899,906,842,624 (or 250). |
Windows' environment variables can be accessed using the $Env: prefix, for example, $Env:SystemRoot or $Env:APPDATA. Alternatively, the entire environment variable namespace can be accessed via the Env: PowerShell , for example:
The .NET Framework Convert class contains numerous methods for performing type casting. To view the methods exposed by this class, type the following:
PowerShell also has its own built-in type casting operator (the -as operator). Unfortunately, this operator doesn't raise an exception when a type casting operation fails (it simply returns $null). For example:
This isn't at all robust and, again, can lead to unpredictable results. The preferred method is therefore to use the Convert class, as described in the section, above.
PowerShell V3.0, and above, supports a number of validation declarations that can be used when accepting parameter input or when initialising variables. The declarations offload certain validation tasks to the PowerShell interpreter, therefore reducing complexity, whilst, at the same time, increasing readibilty and overall robustness.
Validation failures cause one of two types of to be raised, depending on where the declaration is used. If the declaration is used whilst accepting parameter input, a ParameterBindingValidationException exception will be raised; if the declaration is used whilst declaring a variable, a ValidationMetadataException exception will be raised.
The table below describes the available validation declarations:
Declaration | Meaning | Example(s) |
---|---|---|
[ValidateCount(min, max)] | Declares the number elements an array or hash table may have. | [ValidateCount(5, 12)] [String[]] $meetingAttendees |
[ValidateLength(min, max)] | Declares the permitted length of a given string value. | [ValidateLength(2, 40)] [String] $surname |
[ValidateNotNull()] | Declares that a value must not be $null. |
[ValidateNotNull()] [Net.Sockets.TcpClient] $TCPClient |
[ValidateNotNullOrEmpty()] | Declares that a value must not be $null or empty. |
[ValidateNotNullOrEmpty()] [System.IO.File] $filehandle |
[ValidatePattern('<RegEx>')] | Declares that a value must match a given . | [ValidatePattern('^(?:(0\d{4})\s+)?(\d{6})$')] [String] $ukTelephoneNumber [ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')] [String] $soureIP |
[ValidateRange(min, max)] | Declares the permitted range of a given numeric value. | [ValidateRange(0, 65535)] $portnumber |
[ValidateScript({<script block>})] | Declares a script block that must evaluate to $true when processing the given value. The value to be tested is stored in the $_ variable. | [ValidateScript({$_ -ge (Get-Date)})] [DateTime] $meetingDateTime |
[ValidateSet(<set>)] | Declares that the value must exist in the given set. | [ValidateSet('tcp', 'udp', 'icmp')] [String] $protocol |
For further information regarding the use of validation declarations whilst accepting parameter input, please refer to the section.