LearningPowerShell

From HerzbubeWiki
Jump to navigation Jump to search

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


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 to foreach-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 attribute
  • Mandatory 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 and end 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 type System.IO.FileInfo or System.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 causes get-childitem to output objects of type System.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 cmdlet Write-Output). To print a status message from a script, the cmdlets write-host, write-warning or write-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 the get-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 with if (! $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 type System.Management.Automation.ApplicationInfo which represents the executable program. The path can then be obtained from the Path 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 type System.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 type System.Management.Automation.PathInfo, so if you need the result as a simple string then you have to use the property Path. Very important: If you invoke resolve-path on a UNC path, then the resulting path will be prefixed with the string "Microsoft.PowerShell.Core\FileSystem::". However, if you invoke resolve-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 combine resolve-path with the convert-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 uses Systm.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")