<?php include 'HEADER.php'; ?>

<h1>Windows PowerShell</h1>

<p>See the <a href="http://technet.microsoft.com/en-us/library/bb978526.aspx">MS TechNet PowerShell documentation</a>.</p>

<p>Windows PowerShell is a shell scripting environment suitable for system administration tasks. Unlike most admin scripting environments that process text, PowerShell processes .NET objects. PowerShell uses its own new language, rather than repurposing an exiting language like VBScript or Javascript.</p>

<p>Built-in PowerShell commands are <i>commandlets</i> named like <i>verb-noun</i> (e.g.&mdash;Get-Help or Start-Service). Every built-in commandlet provides help with:</p>

<pre>get-help <i>commandlet-name</i> -detailed | more</pre>

<p>Another useful exploration commandlet is <code>get-member</code>. The <code>get-service</code> commandlet (run without any arguments) returns a list of services. Piping that to <code>get-member</code> shows the members and methods of service objects.</p>

<pre>get-service | get-member</pre>

<p>PowerShell has a number of built-in aliases, as seen from <code>get-alias</code>. <code>ls</code> (an alias for Get-ChildItem) does what one expects, as does <code>cat</code> (an alias for get-content). There's no exact analog for 'grep', because everything is object-based rather than text-based. However:</p>

<pre>get-service | where-object {$_.status -eq 'running'}</pre>

<p>Note that PowerShell is case insensitive. You can often turn on case sensitivity for comparison operators by prepending "c" to flags.</code></p>

<pre>PS&gt; 'test','TEST','Test' | ? {$_ -like 'Test'}
test
TEST
Test
PS&gt; 'test','TEST','Test' | ? {$_ -clike 'Test'}
Test
</pre>

<p><code>?</code> is an alias for the <code>where-object</code> commanlet. <code>Where-Object</code> is a filter that evaluates the bracketed expression, and returns it if it evaluates as true. <code>$_</code> is a built-in variable that holds the current argument of the current block/iteration (the current pipeline iteration in this case). See <code>get-help about_automatic_variables</code>. There are a variety of filters, including sort (<code>Sort-Object</code>):</p>

<pre>get-eventlog -newest 25 -logname System | sort -descending -property Source,Message</pre>

<p>Comments are prepended by a hash:</p>

<pre># This is a comment.</pre>

<p>The <code>get-command</code> commandlet shows a list of all available commandlets.</p>

<p>As of late 2012, the current version is PowerShell 3.0.</p>

<h2>Scripts</h2>

<p>PowerShell scripts are normal text files with a <i>.ps1</i> extension. However, execution of scripts is restricted by default (<code>Get-ExecutionPolicy</code> and <code>Set-ExecutionPolicy</code> and <code>Get-Help about_Execution_Policies</code>), and scripts can't be run by double-clicking in any event.</p>

<pre>Set-ExecutionPolicy Unrestricted -scope CurrentUser</pre>

<p>Run a script:</p>

<pre>$home\myScripts\script.ps1</pre>

<p>In Windows 8 and Server 2012, scripts can be run by right-clicking on them and choosing "Run with PowerShell" (though obviously this is only appropriate for scripts that don't require arguments and don't send important output to the console).</p>

<p>Microsoft also includes a lightweight IDE for PowerShell: <code>powershell_ise.exe</code>.</p>

<h2>Powershell Scripting Basic Syntax</h2>

<pre># Hash starts a comment

&lt;# A multi-
   line
   comment.
   These seem to only work in scripts,
   not in the shell.
#&gt;

$_    # The current object, soft of like in Perl
$Args    # Parameters passed to a script, function, or block
$False    # Evaluates to false
$True    # Evaluates to true
$NULL    # Evaluates to null
$Home    # Full path to user's home directory
$Profile    # Path to user's profile
$Pwd    # Current working directory

# Comparison operators
-eq    # equal
-ne    # not equal
-gt    # greater than
-ge    # greater or equal
-lt    # less than
-le    # less or equal
-like  # wildcard pattern matching
-match # regex
-contains    # Test if an item is present in a collection

# Conditionals

if ($foo -eq $True) {
    Write-Host "Yes"
} elseif ($foo -eq "Blue") {
    Write-Host "Blue"
} else {
    Write-Host "Other"
}

switch ($x) {
    "a" { Write-Host "A" }
    "b" { Write-Host "B" }
    default { Write-Host "C-Z" }
}

# Looping

for ($i = 0; $i -le 10; $i++) {
    Write-Host $i
}

foreach ($i in Get-Alias) {
    Write-Host $i.name
}

$i = 0
do {
    Write-Host $i++
} while ($i -lt 10)
</pre>

<h3>Objects</h3>

<p>Object creation varies between COM and .NET objects.</p>

<pre># COM:
$myComObj = New-Object -comobject "InternetExplorer.Application"
$myComObj.visible = $True
$myComObj.Quit

# .NET:
$myNetObj = [System.Net.Dns]::GetHostAddresses("mail.example.com")
$myNetObj | Select-Object IpAddressToString
</pre>

<p>Show the members and properties of an object by piping the object to <code>get-member</code> (which is also available as the alias <code>gm</code>).</p>

<pre>PS H:\> get-wmiobject win32_logicaldisk | gm


   TypeName: System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk

Name                         MemberType    Definition                                        
----                         ----------    ----------                                        
PSComputerName               AliasProperty PSComputerName = __SERVER                         
Chkdsk                       Method        System.Management.ManagementBaseObject Chkdsk(S...
Reset                        Method        System.Management.ManagementBaseObject Reset()    
SetPowerState                Method        System.Management.ManagementBaseObject SetPower...
Access                       Property      uint16 Access {get;set;}                          
Availability                 Property      uint16 Availability {get;set;}                    
BlockSize                    Property      uint64 BlockSize {get;set;}                       
Caption                      Property      string Caption {get;set;}
[...snip...]</pre>

<h3>Functions</h3>

<pre>function My-Function ($bar) {
    return $bar + 1
}
My-Function 1
</pre>

<p>When calling a function <em>don't</em> wrap the arguments in parens.</p>

<h2>Example Script</h2>

<pre># This Powershell script monitors free disk space, and alerts if it
# falls below a certain threshold. -- Paul Gorman 12 March 2015

$percentFreeThreshold = 20
$alertTriggered = $False
$mailServer = "mail.example.com"
$mailFrom = "alert@example.com"
$mailTo = "alert@example.com"
$mailSubject = "Check disk space on server!"
$mailBody = "The script check_drive_space.ps1 on server sees that drive free space has fallen below the acceptable threshold."

function Percent-Free ($disk) {
    return [Math]::Round(($_.FreeSpace / $_.Size) * 100)
}

$drives = Get-WmiObject Win32_LogicalDisk |
    Where-Object {$_.Size -gt 1} # Ignore optical drive

$drives | ForEach-Object {
    if ((Percent-Free $_) -lt $percentFreeThreshold) {
        $alertTriggered = $True
    }   
}

if ($alertTriggered) {
    Send-MailMessage -SmtpServer $mailServer -to $mailTo -from $mailFrom -subject $mailSubject -body $mailBody
}</pre>

<?php include '../FOOTER.php'; ?>
