LearningPowerShell
This page contains my notes about my recurring attempts at working with Microsoft's Windows PowerShell. A remote hope exists that some of these notes might also be useful to other unlucky individuals that are forced into contact with this weird piece of Microsoft technology.
Fast Facts
Windows PowerShell is Microsoft's current official scripting solution. It supersedes all previous solutions such as Windows Script Host, or the well-known but ancient cmd.exe
. PowerShell can be used to interactively enter commands, but also to execute scripts (which have the file extension .ps1
). PowerShell provides a so-called ISE for developing scripts - a piece of software that corresponds to an IDE. The abbreviation spells out as "Integrated Scripting Environment".
Windows PowerShell is an integral part of the system since Windows 7 and Windows Server 2008 R2. On older versions of Windows (e.g. XP, Vista), PowerShell is deployed via service pack or needs to be manually installed. On Windows 7, an interactive PowerShell, or an instance of the ISE, can be started via "Start > Accessories > Windows PowerShell". Alternatively the shell can be started by executing powershell.exe
on the command line.
The Wikipedia entry provides a good overview of the different versions of PowerShell. PowerShell 1 and 2 work with the .NET runtime v2. PowerShell 3 works with the .NET runtime v4, but is not available on older Windows systems (e.g. XP).
References
- Main article on MS Technet
- Wikipedia
- stackoverflow.com questions with the tag "powershell"
- Running Windows PowerShell Scripts (article on technet.microsoft.com). A summary of this article can be found on this page in the section Scripts.
- Windows PowerShell: Scripting Crash Course (article on technet.microsoft.com)
- PowerShell Reference at ss64.com: The best reference on syntax, operators and commands for PowerShell 2.0 that I have found so far
- Help on Cmdlets that are provided by the TFS PowerTools:
C:\Program Files\Microsoft Team Foundation Server 2010 Power Tools\Help\PowerShellCmdlets.mht
Why I use PowerShell
As can be surmised from the comments at the top of this page, my opinion about PowerShell is not favourable. As a beginner I positively hated Powershell because I just couldn't seem to do anything right. I also felt that PowerShell's weak attempts to cater to the seasoned Unix shell user were rather laughable. These days, after prolonged but infrequent use, I still detest PowerShell because it continues to give me unpleasant surprises at every possible turn, and simple problems almost never have simple, or at least intuitive solutions. Just look at the size of the Recipes section further down. Another thing that drives me crazy are the insanities revolving around arrays (see the Arrays section).
In a nutshell, I don't like PowerShell because it has far too many quirks, inconsistencies and unexpected behaviour for my taste (see the Principle of Least Surprise). Another contributing factor is the weird object pipeline system: Although the concept looks extremely cool at first glance, I have had a lot of difficulties grokking it. I cannot deny, though, that my focus on Unix might have limited my capacity for understanding.
So why do I still use PowerShell?
Well, sometimes you just have to disregard your personal likes or dislikes - typically in a professional environment where there are many outside influences that you can't shape as you would like. In such an environment PowerShell's pros can quickly start to outweigh its cons. Here is my summary:
- Powershell is Microsoft's official scripting solution that supersedes older solutions such as Windows Script Host. In a Windows-centric environment it is therefore only natural to use PowerShell for the task of automating stuff.
- I get PowerShell for free because it is pre-installed on all modern Windows systems. So why should I bother with deploying a third-party scripting solution? With the first-class citizen status that PowerShell has on a Windows system, I can expect the following benefits:
- No compatibility issues
- Updates are automatically installed whenever I choose to run Windows updates
- Long-term support is guaranteed
- No quarrels with the IT departement (which I might have if I wanted to install "evil" third-party software)
- Similarly, acceptance among co-workers may be higher
- Even though PowerShell is not my darling, it is a positive joy to write PowerShell scripts compared to writing DOS batch scripts
- PowerShell allows me to use .NET, which really makes it extremely powerful
Basic concepts
Cmdlet
Cmdlet is pronounced "command-let". A cmdlet is a single PowerShell command. A cmdlet is executed within the PowerShell process, not as a separate process.
Cmdlets follow the naming scheme verb
-noun
and have - as one would expect - parameters. A standard parameter is -?
, which is used to show the help text for a cmdlet.
The usual pipeline character together with the more
cmdlet is used to paginate long output:
get-help | more
Aliases
After working with PowerShell for a while, you will start to wish for a way to shorten the long names of some cmdlets. This can be done with aliases. PowerShell already has a number of predefined aliases, e.g. the good old dir
command is an alias for the cmdlet get-childitem
.
To display all the currently defined aliases:
get-alias
More help on the topic of aliases:
man alias
(man
is an alias for Get-Help
, which is known to those who, like me, are used to work on the Unix shell; nicely enough, man
saves some typing because it already has the more
pipeline built in).
Pipeline
Cmdlets can be chained with the pipe character ("|") to form a pipeline:
cmdlet1 | cmdlet2 | ...
Every cmdlet in a pipeline passes its output as input to the cmdlet that comes next in the pipeline. Data is not passed between cmdlets as simple text (i.e. a character stream), but as fully typed objects! Even if it looks like as if a cmdlet passes text to its successor in the pipeline, it does this by passing objects of type System.String
.
How to inspect objects
The cmdlet get-member
can be used to inspect an object in order to determine its type and its member elements. For instance:
# A single object is passed through the pipeline "foo" | get-member # -inputobject can be used if the object is not passed through the pipeline get-member -inputobject "foo" # A single array object is passed through the pipeline. get-member detects that # the object is a collection, therefore it does NOT print information about the # collection object but instead it prints information about the elements of # the collection. Because in this case all collection elements have the same type # get-member only prints out a single block of type information. dir | get-member # --inputobject must be used to obtain information about the collection object # (an array) itself. get-member -inputobject (dir) # This time the collection contains elements that have different types. get-member # prints a block of type information for each type it encounters. @("foo", 42) | get-member
The object type is printed in the first line of the output. The following lines then list all member elements (properties and methods) of the object. If there are many members it might make sense to only show members of a certain type. Read the man page to learn more:
man get-member
By default get-member
lists only instance member elements. If you want to see static member elements as well, use the -static
parameter:
"foo" | get-member -static
Object member elements
Every object has these types member elements:
- Properties
- Methods
These are some examples how to get the value of a property, or how to execute a method:
$os = get-wmiobject -class Win32_OperatingSystem # Returns the value of the property "OSArchitecture", an object of type System.String $os.OSArchitecture # Invokes the method toString() $os.tostring()
It is possible to access member elements without storing the object in a variable:
(get-wmiobject -class Win32_OperatingSystem).OSArchitecture (get-wmiobject -class Win32_OperatingSystem).tostring()
You use foreach-object
to invoke member elements on each object that appears in a pipeline. Example:
get-childitem . -name | foreach-object { $_.ToUpper() }
Notes:
get-childitem
passes the names of all files and subfolders of the current folder toforeach-object
in the form of string objects.- The code block that is delimited by "{}" is invoked on each of the string objects.
- The variable
$_
references the string object. - The code block in this example consists of a single method call, but there is no limit on the complexity it can have
PowerShell and .NET
Upon inspecting objects with get-member
you will note that some of them have types that are well-known in the .NET environment. This may come as a surprise, but it is only natural since .NET is entirely based on .NET. For instance, if you see an object that has the type System.String
, you can invoke all the string methods on that object that you know from developing in C# or some other .NET language.
$result = $string1.CompareTo($string2)
The cmdlet new-object
is used to create a new instance of a .NET type. Example:
$googleURI = new-object -typename System.Uri -argumentlist "http://www.google.ch/"
Brackets ([]) are used to access static types, "::" is used to access static member elements. Examples:
[System.Environment] | get-member [System.Environment] | get-member -static [System.Environment]::Commandline # static property [System.Environment]::GetLogicalDrives() # static method
The version of PowerShell determines the version of the .NET runtime. At the moment the following is known:
- PowerShell 1 und 2 use .NET 2
- PowerShell 3 uses .NET 4
Executing PowerShell scripts
Execution policies
PowerShell uses so-called "execution policies" to determine which scripts can or cannot be executed. The policy that is in use for the current session can be determined with this cmdlet:
Get-ExecutionPolicy
The default policy is "Restricted", which means that on a newly installed system no PowerShell scripts can be executed (single cmdlets in the console are OK, though). This help page shows the execution policies that exist, what their purpose is, and how they can be activated:
man about_execution_policies
Example: To enable execution of arbitrary scripts for the user that is currently logged in:
Set-ExecutionPolicy unrestricted -scope CurrentUser
If you don't specify the -scope
parameter, the policy is applied to the entire system (the default scope is LocalMachine
). Only one execution policy can be in effect at any given time. Which one it is is determined by the priority of the scope:
- MachinePolicy (höchste Priorität)
- UserPolicy
- Process
- CurrentUser
- LocalMachine (lowest priority)
To view the execution policies for all scopes:
Get-ExecutionPolicy -list
Executing scripts
One thing in advance: It is not possible to execute PowerShell scripts by double-clicking .ps1
files in Windows Explorer. Period. The reason: Security.
To execute a script on the command line:
foo.ps1 # Script is located in a folder that is listed in the PATH environment variable .\foo.ps1 # Relative path, in this case to the current working folder; the current working folder is not in PATH C:\path\to\foo.ps1 # Absolute path & "C:\path with spaces\foo.ps1" # Absolute path that contains a space character
Scripts can also be executed in one of the following ways:
- Right-click on a .ps1 file in Windows Explorer, then select "Run with PowerShell" from the context menu
- Enter one of the following lines into the "Run..." input field in the start menu. If you don't specify the
-noexit
parameter the shell window will be automatically closed after script execution ends.
powershell -noexit C:\path\to\foo.ps1 powershell -noexit &'C:\path with spaces\foo.ps1'
- Create a shortcut to a .ps1 file, then in the shortcut properties change the command line so that it looks like one of the above "Run..." examples.
DOS batch scripts
PowerShell scripts can be executed from within a DOS batch script like this:
powershell -file "C:\path\to\foo.ps1" -param1 -param2
Notes:
- It is important to run
PowerShell.exe
, not the script itself - Because we enclose the script path with double quotes, the path to the PowerShell script may even contain space characters
Scheduled Tasks
PowerShell scripts can be executed as a Scheduled Task like this:
PowerShell.exe -Command /path/to/scriptfile
To specify parameters:
PowerShell.exe -Command "/path/to/scriptfile -param1 -param2"
Programming scripts
Syntax
The basic PowerShell syntax is not explained here, this would be beyond the scope of this wiki page. Read the articles that are listed in the References section to get an idea. Another source of information is Google.
Function declarations
A basic function declaration. The function has no parameters.
function Foo { [...] }
A function with some parameters
function Foo($bar, $baz) { [...] }
Parameters can also be declared with a type so that the function must be invoked with values that conform to the specified types. Warning: Powershell tries very hard to convert supplied parameters to the required type. If a parameter's type is string
then just about anything can be converted!
function Foo([string] $bar, [System.IO.FileInfo] $baz) { [...] }
An alternative way how to define a function's parameters is by using a <code<param block. This makes the parameter declaration more readable when sophisticated Powershell features are used. Example:
function Foo { param ( [Parameter(Mandatory=$True)] [string] $computername, [Parameter(Mandatory=$True)] [string] $logfile, [int] $attemptcount = 5 # default value ) }
Script == Function?
From a conceptual point of view, a PowerShell script is simply a PowerShell function that has been placed inside a file. Looking from the outside, the name of the script file is the name of the function. Looking from the inside, i.e. looking at the script source cocde, the function has no name.
Script parameters
The input parameters of a script must be declared at the top of the script code. Only comments can be placed further up than the input parameter declaration. This is how the last example from the function declarations section above looks like when adapted to a script:
[CmdletBinding()] param ( [Parameter(Mandatory=$True)] [string] $computername, [Parameter(Mandatory=$True)] [string] $logfile, [int] $attemptcount = 5 # default value )
A script test.ps1
that has the above parameter declaration can be executed like this:
./test -computername SERVER -logfile err.txt ./test -comp SERVER -log err.txt -attempt 2 ./test SERVER err.txt 2 ./test -log err.txt -attempt 2 -comp SERVER
Notes:
- Parameter names can be shortened as long as they remain unambiguous
- Parameter values can be specified without parameter names if the order of the values matches the order in which parameters are declared inside the script. This is not recommended unless maybe for a bit of quick & dirty interactive work.
- The order of parameters does not matter if they are identified by name
Parameter
in the code example is a so-called attributeMandatory
in the code example is a so-called argument of the attribute
More information about parameter declarations can be had with
man about_functions_advanced_parameters
Input from the pipeline
Example how to receive and process a list of string objects from the pipeline. Note that the exact same syntax (param/begin/process/end) can be used within a function.
[CmdletBinding()] param ( [Parameter(ValueFromPipeline=$True)] [string] $fileName ) begin { echo "Fileliste" echo "---------" $numberOfFiles = 0 } process { if (! $fileName) { echo "No input received" } else { echo $fileName $numberOfFiles++ } } end { echo "----------------------------" echo "Number of files: $numberOfFiles" }
Notes:
- The
begin
andend
block are executed exactly once - The
process
block is executed once for each object that is received from the pipeline - The
process
block is executed exactly once if the script does not receive any input from the pipeline
Writing a cmdlet
TODO: Is a script a cmdlet?
Snap-ins
The standard functionality of PowerShell can be extended with the help of so-called "Snap-ins". A well-known example is the snap-in that is provided by the TFS PowerTools. The following examples all work with this snap-in. The name of the snap-in is
Microsoft.TeamFoundation.PowerShell
A snap-in must be loaded before its functionality can be used:
add-pssnapin Microsoft.TeamFoundation.PowerShell
Check whether a snap-in is already loaded:
get-pssnapin Microsoft.TeamFoundation.PowerShell
Check whether a snap-in is registered on the computer and can be loaded:
get-pssnapin -registered Microsoft.TeamFoundation.PowerShell
This scnippet combines the examples above and can be placed at the beginning of a script to make sure that a snap-in is loaded:
$snapin = "Microsoft.TeamFoundation.PowerShell" if ((get-pssnapin $snapin -ErrorAction "SilentlyContinue") -ne $null) { # Snap-in is already loaded, we don't have to do anything } elseif ((get-pssnapin $snapin -registered -ErrorAction "SilentlyContinue") -ne $null) { # Snap-in is not loaded, but it's registered, so we can load it add-pssnapin $snapin } else { write-host "PowerShell snap-in $snapin not found - are the TFS PowerTools installed on this computer?" -foregroundcolor Red }
Windows Registry
In PowerShell parlance, a Windows Registry key corresponds to an "Item" (hence the use of the Get-Item
and Set-Item
cmdlets), while a Window Registry value corresponds to an "ItemProperty" (hence the use of the Get-ItemProperty
and Set-ItemProperty
cmdlets).
Note that write operations may require Administrator permissions, depending on which part of the Windows Registry is modified.
Read a Windows Registry value:
$foo = Get-ItemProperty -Path hklm:"registry/key/name" -Name "value name" echo $foo.KeyName
Check whether a Windows Registry value exists:
$foo = Get-ItemProperty -Path hklm:"registry/key/name" -Name "value name" If ($foo -ne $null) { # Windows Registry key exists }
Create a new Windows Registry value, or overwrite an existing one. If the value does not exist it is created.
Set-ItemProperty -Path hklm:"registry/key/name" -Name "value name" -Value "value"
Delete a Windows Registry value:
Remove-ItemProperty -Path hklm:"registry/key/name" -Name "value name"
Create a new Windows Registry key. "> $null" suppresses the output of the operation. The -force
parameter makes sure that any missing intermediate keys are created as well.
New-Item -Path hklm:"registry/key/name" -force > $null
Recursively delete a Windows Registry key and its children:
Remove-Item -Path hklm:"registry/key/name" –recurse
Strings
Check whether a string has a given value:
if ($aString -eq "foo") [...] # Check with Regex if ($aString -match '^foo[0-9]*') [...]
Split a string into parts:
"foo;bar" -split ";" "foo;bar`nfoobar" -split "[;`n]" # with regexp it is possible to specify more than one separator
Remove whitespace at the beginning/end of a string (trim):
" `t`n foobar `t`n ".Trim() " `t`n foobar `t`n ".TrimStart() " `t`n foobar `t`n ".TrimEnd()
The -replace
or -creplace
(case sensitive) parameters can be used to replace substrings inside a string. One of the many, many surprises of PowerShell is that the default replace operation is case-insensitive. Also important: You can use Regex!
"FooBarFoo" -replace "foo$", "" >>> FooBar "foobarfoo" -creplace "foo$", "" >>> foobar
Collections
Arrays
Create array objects:
$anArray = @() $anArray = @( "value1", "value2" )
Insert and remove elements into/from an array. Important: Arrays are immutable. The operator +=
behind the scenes creates a new array that contains the new element. Why there is no operator -=
that does the same for removal is the PowerShell designers' well-kept secret.
$anArray = @("a", "b", "c", "d", "e") $anArray += "f" # Array now contains "a", "b", "c", "d", "e", "f" $anArray = $anArray | where-object {$_ -ne "b"} # Array now contains "a", "c", "d", "e", "f" $anArray = $anArray[0..2] + $anArray[4] # Array now contains "a", "c", "d", "f"
The -contains
parameter is used to check if an object exists within an array:
$anArray = @("one", "two", "three") if ($anArray -contains "two") { # do something }
Sort an array using the cmdlet sort-object
:
get-childitem /path/to/folder -name | sort-object -descending
To count the number of items in an array, use the count
property. Also read the next two items!
(get-childitem /path/to/folder -recurse -filter "*.ps1").count
In the above example, if get-childitem
finds 0 or 1 file I would expect the property count
to return the value 0 or 1. However, the property returns "nothing". The reason: Unfortunately, get-childitem
does not return a collection if it finds 0 or 1 file. Instead, it returns no object (if it finds 0 files), or a single object (if it finds 1 file). In both cases the property count
does not exist because the result is not an array. The workaround is to wrap the result of get-childitem
using the so-called "Array SubExpression operator" @()
. This makes sure that the result is always an array. If the result already is an array, it remains unchanged (in other words: the operator does not create an array-within-an-array). Example (compare with the previous example):
@(get-childitem /path/to/folder -recurse -filter "*.ps1").count
The above is already pretty illogical, but here comes a prime example of how insane PowerShell can be. The innocent question is: How can I return an array with 0 or 1 elements from a function? Aka: How do I behave better than get-childitem
? When a function returns an array that contains 0 or 1 elements, PowerShell causes that array to be processed before the function actually returns (supposedly the reason for this behaviour is called "the streaming processing model" of PowerShell, but I don't recall where I have been reading this). Instead of an array with 0 elements, the function returns a null value. Instead of an array with 1 elements, the function returns the single object itself. In both cases the caller does not receive an array as expected. The solution for the problem is to suppress the processing of the return value; this is achieved by prefixing the return value with a comma (,). Example:
Function Foo { $emptyArray = @() return ,$emptyArray }
Another PowerShell trap is the way how arrays overload the -eq
operator. Let's say you have a function with a parameter that can be either an array, or $null
. So how do you distinguish between the two cases? The logical thing to do would be to write this condition: if ($myParameter -eq $null)
. But this doesn't work, because arrays overload the -eq
operator in a very specific way so that it works completely different from what one would expect:
- The operator first checks whether the array contains the operand
- If it does the operator returns a new array which contains the operand as its sole value
- If it does not the operator returns a new array that is empty
This means that if someone passes an array containing $null
into the hypothetical function, the naive condition if ($myParameter -eq $null)
would surprisingly be true, because the -eq
operator returns @($null)
which, when evaluated in a boolean context, results in $True
. The workaround (I hesitate to call this "solution") is to reverse the order in which the operands appear: if ($null -eq $myParameter)
.
Dictionaries
Create dictionary objects:
$aDictionary = @{} $aDictionary = @{ "key1" = "value1"; "key2" = "value2" } $aDictionary = @{ $key1 = $value1; $key2 = $value2 }
Add, modify, remove dictionary entries:
$aDictionary.Add($key, $value) $aDictionary.Set_Item($key, $value) # Fügt den Eintrag hinzu, falls er nicht existiert $aDictionary.Remove($key)
Accessing entries:
$value = $aDictionary.$key $value = $aDictionary."foo" if ($aDictionary.ContainsKey($key)) [...] if ($aDictionary.ContainsValue($value)) [...] $allKeys = $aDictionary.Keys $allValues = $aDictionary.Values
Iterate over a dictionary's entries:
foreach ($dictionaryEntry in $aDictionary.GetEnumerator()) { $key = $dictionaryEntry.Key $value = $dictionaryEntry.Value }
Merge two dictionaries:
$newDictionary = $aDictionary + $anotherDictionary
Use @
as so-called "SPLAT operator", to use the content of a dictionary as the parameters of a cmdlet:
test-path @aDictionary
Recipes
This section contains recipes, i.e. short re-usable solutions to short general questions. I often find myself adding to this list after cursing PowerShell because it seems quaint, complicated, illogical, stupid, or plain buggy.
- What is the PowerShell escape character (e.g. to embed a double quote inside a string)?
- The escape charater is ` (Accent Grave). Example:
echo "foo said `"bar`""
- How can I tell
get-childitem
not to format its output, but to simply output a list of files (1 file per line)? - The way the question is formulated exposes that at the time I asked the question I did not understand that
get-childitem
outputs objects, not lines of text.get-childitem
usually outputs objects of typeSystem.IO.FileInfo
orSystem.IO.DirectoryInfo
. The tabular formatting happens only in the final output seen by the user. The closest you can get to what the question asks is to use the parameter-name
, which causesget-childitem
to output objects of typeSystem.String
. Example:
get-childitem /path/to/folder -recurse -name
- How do I print a line of text?
- The alias
echo
outputs text that can be processed in a pipeline or used as the return value from a function (the alias invokes the cmdletWrite-Output
). To print a status message from a script, the cmdletswrite-host
,write-warning
orwrite-error
should be used. Examples:
echo foo write-host "foo" write-host -foregroundcolor red "foo" write-error "boo!"
- How do I check in a script if an input parameter was not specified?
- The comparison
$inputParameter -eq $null
does not work. The following works, though:
if (! $inputParameter) { ... }
- How do I check whether a file item is actually a folder?
- Use the
PSIsContainer
property. Example:
$subfolders = get-childitem -path "path/to/root/folder" -recurse | where {$_.PSIsContainer -eq $true}
- How can I eliminate duplicates from a list of strings (the "sort | uniq" pattern for those who are familiar with the Unix shell)?
- Use the cmdlet
select-object
. The cmdlet internally sorts the input list. Example:
cat /path/to/file | select-object -unique
- How do I simulate Unix
grep
? - Because the PowerShell architecture works with objects and not text, it is difficult to simulate the behaviour of Unix
grep
. The following snippet is an example how to search the output of theget-alias
cmdlet for a string:
(get-alias | out-string) -split "`n" | select-string get-member
- Another option that is more tuned to PowerShell's object philosophy is the
where-object
cmdlet. The cmdlet applies a filtering script block on each object that it receives in a pipeline. If the script block evaluates to$True
the object is passed on to the next cmdlet in the pipeline. If the script block evaluates to$False
the object is not passed on. Example:
get-childitem -path "/path/to/folder" -recurse | where-object -filterscript { $_.Name.EndsWith(".ps1") }
- How do I declare a script parameter that acts as a switch (true/false), i.e. that has no arguments?
- Use the
[switch]
declaration. If the switch is set the parameter has the value$true
, otherwise it has the value$false
. Example:
[Parameter(Mandatory=$False)] [switch] $sort-ascending
- How do I get at the content of an environment variable?
- This page has a summary with the cmdlets that are important for working with environment variables. The following example shows the simplest way how to get the content of an envvar as a string. If the envvar is not set the
$aString
variable has no value (can be checked withif (! $aString) [...]
).
$aString = $env:MY_ENVIRONMENT_VARIABLE
- How do I get the path of an executable program as string (the Unix equivalent is
which
)? - The
get-command
cmdlet outputs an object of typeSystem.Management.Automation.ApplicationInfo
which represents the executable program. The path can then be obtained from thePath
property. Example:
$executable = get-command regsvr32.exe $executablePath = $executable.path
- How do I simulate the DOS command
pause
? - Use the following two lines. Hint: It is possible to find out which key the user pressed by examining the object in
$keyInfo
. The object has the typeSystem.Management.Automation.Host.KeyInfo
.
write-host "Press any key to continue ..." $keyInfo = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
- How do I simulate Unix
dirname
?
echo "c:\path\to\text.txt" | split-path # results in c:\path\to
- How do I determine the absolute path of a file or folder if I currently have a relative path only?
- Use the
resolve-path
cmdlet. Disadvantage: The file or folder must exist!resolve-path
returns an object of typeSystem.Management.Automation.PathInfo
, so if you need the result as a simple string then you have to use the propertyPath
. Very important: If you invokeresolve-path
on a UNC path, then the resulting path will be prefixed with the string "Microsoft.PowerShell.Core\FileSystem::". However, if you invokeresolve-path
with a filesystem path from one of your local drives, then the prefixing does not occur! To be sure that you always get a normalized path in string form you have to combineresolve-path
with theconvert-path
cmdlet.convert-path
not only removes the extraneous prefix, it also converts the result into a simple string. Examples:
cd c:\windows\system32 # Result = c:\windows resolve-path .. | convert-path # Result = Microsoft.PowerShell.Core\FileSystem::\\server\share\folder\foo.txt (resolve-path \\server\share\folder\foo.txt).Path # Result = \\server\share\folder\foo.txt resolve-path \\server\share\folder\foo.txt | convert-path
- How do I determine the path of the PowerShell script that currently executes?
- There is no universal solution for this, you must choose the correct solution depending on the scope in which the determining code needs to be executed (WTF).
- Solution 1: Code is executed in the scope of a function
$scriptPath = $MyInvocation.ScriptName
- Solution 2: Code is executed outside of any functions, i.e. in the "root" scope of the script
$scriptPath = $MyInvocation.MyCommand.Path
- How do I simulate Unix
pwd
(the current working folder)? - Do not use
[System.IO.Directory]::GetCurrentDirectory()
, this function returns the current working folder of the PowerShell instance (which usually is the folder from which the instance was launched). The right thing to do is
get-location
- How do I join two paths (e.g. append a file name to a folder path)?
- Use the cmdlet
join-path
. Simple usage with 2 path components:
$folderPath = "c:\windows\system32" $fileName = "regsvr32.exe" $filePath = join-path $folderPath $fileName
- Slightly more complicated usage with more than 2 path components:
$folderPath = "c:\folder" $subfolderPath = "subfolder" $subsubfolderPath = "subsubfolder" $fileName = "file.txt" $filePath = join-path $folderPath $subfolderPath | join-path -childpath $subsubfolderPath | join-path -childpath $fileName
- How do I check if a folder or file exists?
- Use the cmdlet
test-path
.
if (! (test-path -pathtype container $folderPath)) [...] if (! (test-path -pathtype leaf $filePath)) [...] if (! (test-path -pathtype any $aPath)) [...]
- How do I get the exit code of a program that I just executed?
- The only reliable method that I have found is to use the .NET class
System.Diagnostics.Process
. When I last researched this topic, the PowerShell global variables$LastExitCode
and$?
were not reliably filled with a value. More research is needed to find out the reason for this. Here's an example that usesSystm.Diagnostics.Process
:
$process = new-object System.Diagnostics.Process $process.StartInfo.Filename = "c:\windows\system32\regsvr32.exe" $process.StartInfo.Arguments = "/s /c c:\path\to\com\dll" $processWasStarted = $process.Start() if (! $processWasStarted) { write-error "Cannot execute program: $processFileName" return 1 } $process.WaitForExit() if ($process.ExitCode -ne 0) { write-error "Error executing $processFileName" return 1 }
- How can I find out whether the current script is executed with Administrator privileges?
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object System.Security.Principal.WindowsPrincipal($identity) if (! $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { write-error "No admin privileges" return 1 }
- How do I perform a bitwise-or operation?
- The bit operators are
-bor
,-band
and-bxor
. Example:
echo 2 -bor 4 # result is 6
- How can I read a file in the .ini format?
- An .ini file without sections can simply be read with the
ConvertFrom-StringData
cmdlet. The result is a dictionary. If an .ini file contains sections then I currently don't know of any simple solutions.
# get-content provides the file's content line-by-line. Using a foreach-object # loop we join the individual lines into a single giant string, with the # individual lines being separated from each other with a newline character. # The result is then processed by ConvertFrom-StringData, which searches the # giant string for key/value pairs and results a dictionary with the data it # finds. ConvertFrom-StringData conveniently filters all comment lines for us. # Additional comments for ConvertFrom-StringData: # - Requires that every key/value pair occurs on a separate line. This is # the reason why the .ini file lines in the giant string are separated # with a newline character. # - ConvertFrom-StringData interpretes backslash characters ("\") as the # beginning of an escape sequence. We want to prevent this because # backslash characters in the .ini file should make it into the dictionary # unchanged for later processing. Our workaround is to replace single # backslashes with TWO backslashes. Because the -replace function works # with Regex, and Regex treats a backslash as the beginning of an escape # sequence, the search expression for a single backslash must be specified # as a double backslash. $aDictionary = ConvertFrom-StringData((get-content $iniFilePath | foreach-object { $_ -replace "\\", "\\" }) -join "`n")