PowerShell
Functions are declared with the function keyword and the body in braces.
function Hello-World
{
Write-Output "Hello, World!"
}
Positional parameters can be referenced using the $args array, which contains all arguments passed to the function on invocation. PowerShell appears to automatically handle arrays passed in as the first argument and interpolates spaces between each item this way.
function Hello-World
{
Write-Output "Hello, $($args[0])!"
}
In order to set a default value, apparently a named parameter has to be used. There are two ways to do this, with or without the param keyword. These parameters are still usable as positional parameters, determined by the order in which they are defined (?), but note that the case of the identifier is preserved to define the option (i.e. "$name" becomes "-name", etc).
function Hello-World($Name='World')
{
Write-Output "Hello, $($Name)!"
}
Using param, multiple parameters can be defined in a param block, which would probably make more sense for complicated functions using multiple parameters. Here the single parameter is also typed.
function Hello-World
{
param (
[string]$Name='World'
)
Write-Output "Hello, $($Name)!"
}
<!-- Documentation or comment-based help is incorporated by means of specially formatted comments within the body of the function. Even without comments, PowerShell will attempt to provide help to a user from the command-line using Get-Help.
``` -->
## Control flow
```powershell
if ($condition) { <# ... #> }
switch ($reference)
{
$value1 { ... }
$value2 { ... }
}
while ($true)
{
(++$Tick)
if ($Tick -gt 2)
{
break
}
} # => @(1,2,3)
do
{
'Hello, world!'
} while ($false) # => @('Hello, world!')
Loops are implemented with ForEach-Object
.
1..5 | ForEach-Object {$_ + 2} # => @(3,4,5,6,7)
When values are stored in a variable at the end of a pipeline, it will create an array. while
and do while
loops are available, as well as until
and do until
loops which operate so long as their condition is false.
Variables
Variables are accessed by prefixing the identifier with $
.
- [Automatic variables][Automatic variable] ($$, $_, $PSVersionTable, $IsLinux, etc) are PowerShell-specific.
- Windows environment variables are actually accessed through the
Env
virtual drive using syntax like$Env:APPDATA
,$Env:USERNAME
, etc.
Typing
[double]$Price
[int]$Quantity
[string]$Description
$Number = [int]'04'
$FailedCast = [int]'Hello'
Hash tables
$fruit = @{
Apple = 'red'
Orange = 'orange'
Eggplant = 'purple'
}
# Inline
$fruit = @{ Apple = 'red'; Orange = 'orange'; Eggplant = 'purple' }
$fruit = @{}
$fruit.Add('Apple','red')
$fruit.Add('Orange','orange')
$fruit.Add('Kiwi','green')
# Deep copy or "clone" of a hashtable.
$fruitclone = $fruit.Clone()
$fruit.Keys # => @('Apple','Orange','Kiwi')
$fruit.Values # => @('red','orange','green')
$fruit.Count
$fruit.Remove('Apple')
Unlike Python, a hash table can be made ordered, changing its data type:
$fruit = [ordered]@{ Apple = 'red'; Orange = 'orange'; Eggplant = 'purple' }
$fruit.GetType().Name # => OrderedDictionary
Documentation
<#
.SYNOPSIS
This script coordinates the process of creating new employees
.DESCRIPTION
This script creates new users in Active Directory...
.PARAMETER UserName
The official logon name for the user...
.PARAMETER HomeServer
The server name where the user's home folder will live...
#>
<#
.EXAMPLE
New-CorpEmployee -UserName John-Doe -HomeServer HOMESERVER1
This example creates a single new employee...
#>
Functions
Named parameters can be declared in one of two ways.
- Within the function body using the param keyword, followed by the name of the variable representing the parameter's value, enclosed in
$(...)
: - Directly after the function name in parentheses, with each parameter separated by a comma.
The name of the variable becomes the named parameter used when invoking the function. Default values for parameters can be specified by placing them within the parentheses.
Parameters can be made mandatory by preceding the parameter name with [Parameter(Mandatory=$true)]
.
Parameters can be static typed by preceding the parameter identifier with the data type in brackets.
function Get-LargeFiles
{
Get-ChildItem C:\Users\Michael\Documents |
where {$_.Length -gt $args[0] and !$_PSIscontainer} |
Sort-Object Length -Descending
}
function Get-LargeFiles($Size)
{
# param ($Size)
Get-ChildItem C:\Users\Michael\Documents |
where {$_.Length -gt $Size -and !$_.PSIsContainer} |
Sort-Object Length -Descending
}
function Get-LargeFiles
{
param ($Size)
Get-ChildItem C:\Users\Michael\Documents |
where {$_.Length -gt $Size -and !$_.PSIsContainer} |
Sort-Object Length -Descending
}
function Get-LargeFiles
{
param ($Size=2000)
Get-ChildItem C:\Users\Michael\Documents |
where {$_.Length -gt $Size -and !$_.PSIsContainer} |
Sort-Object Length -Descending
}
function Get-LargeFiles
{
param ([int]$Size=2000)
Get-ChildItem C:\Users\Michael\Documents |
where {$_.Length -gt $Size -and !$_.PSIsContainer} |
Sort-Object Length -Descending
}
Get-LargeFiles -Size 2000
Switch parameters are typed as a switch
data type.
Boolean values can be explicitly set upon invocation using this syntax:
function Switch-Item
{
param ([switch]$on)
if ($on) { "Switch on" }
else { "Switch off" }
}
Switch-Item # => Switch off
Switch-Item -On # => Switch on
Switch-Item -On:$false # => Switch off
Attach common parameters to a custom function by placing the [CmdletBinding()]
within the body of a function.
This allows use of options like -Verbose
or -Debug
with custom functions.
Now, using Write-Verbose
and Write-Debug
within the function body serve the dual purpose of outputting additional information at the time of execution, when needed, as well as documentation.
Remote administration
Powershell remoting can be done explicitly or implicitly. Remoting relies on WinRM, which is Microsoft's implementation of WSMAN.
- Explicit remoting is also 1-to-1 remoting, where an interactive Powershell prompt is brought up on a remote computer.
- One-to-many or fan-out remoting is possible with implicit remoting, where a command is transmitted to many computers.
Unit testing
Pester tests are organized in a hierarchy of blocks and run with Invoke-Pester
:
Describe
{
Context # optional
{
It
{
Should # assertion statements accept a value passed in via pipe and **must** be called within a `Describe` block
}
}
}
New-Fixture deploy Foo
function Foo
{
# ...
}
Describe 'Foo'
{
$true | Should -Be $true
}
The block in braces is actually an argument pass to the -Fixture
parameter.
Describe "Best airports in the USA" -Fixture
{
It -Name "RDU is one of the best airports" -Test
{
$Output = Get-Airport -City "Raleigh"
$Output | Should -BeOfType System.Collections.Hashtable
}
}
Tasks
Get-ComputerInfo -Property CsName # gin.CsName
$Env:computername
Add-Type -AssemblyName 'System.Web'
[System.Web.Security.Membership]::GeneratePassword(20, 3)
$cred = Get-Credential
$pw = ConvertTo-SecureString "Password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("FullerP", $pw)
Profile
- Similar to a bashrc file, PowerShell has various profiles which can contain custom variable and function definitions, accessible through automatic variables like $PROFILE, etc.
These are loaded using syntax similar to that of bash:
Reload profile
. $PROFILE
Filtering
-
Filtering results can be done with 5 commands:
Where-Object
(aliased towhere
and?
): the most commonly used such commandSelect-Object
(aliased tosc
ed to specify specific columns of information to be displayedSelect-String
(aliased tosls
)ForEach-Object
(aliased toforeach
and%
) There are two different ways to construct aForEach-Object
statement:- Script block, within which the variable
$_
represents the current object - Operation statement, more naturalistic, where you specify a property value or call a method.
- Script block, within which the variable
Loop examples
-
Download files
1..24 | ForEach-Object { Invoke-WebRequest -OutFile ("TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity.m4v" -f $_) ("https://securedownloads.teach12.com/anon.eastbaymedia-drm/courses/3466/m4v/TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity.m4v?userid=$USERID&orderid=$ORDERID&courseid=$COURSEID&FName=TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity" -f $_)}
Processing multiple filesGet-ChildItem . | ForEach-Object { ffmpeg -i $_.Name $_.Name.Replace('m4a','mp3') }
Alert when connection re-establishedwhile ($true) { if ((Test-NetConnection 8.8.8.8 -WarningAction SilentlyContinue).PingSucceeded -eq $true) { [System.Console]::Beep(1000,100) break } }
New domain controller
[Jones][Jones]
Install-WindowsFeature AD-Domain-Services,DHCP -IncludeManagementTools
Install-ADDSForest -DomainName corp.packtlab.com
Add-DhcpServerv4Scope -Name "PacktLabNet" -StartRange 10.0.0.50 -EndRange 10.0.0.100 -SubnetMask 255.255.255.0
Set-DhcpServerv4OptionValue -DnsDomain corp.packtlab.com
Add-DhcpServerInDC -DnsName dc.corp.packtlab.com
New-AdUser -SamAccountName SysAdmin -AccountPassword (Read-Host "Set user password" -AsSecureString) -Name "SysAdmin" -Enabled $true -PasswordNeverExpires $true -ChangePasswordAtLogon $false
Add-ADPrincipalGroupMembership -Identity "CN=SysAdmin,CN=Users,DC=corp,DC=packtlab,DC=com","CN=Domain Admins,CN=Users,DC=corp,DC=packtlab,DC=com"
Get-ADPrincipalGroupMembership sysadmin
Text to speech
-
Text to speech
Add-Type βAssemblyName System.Speech # (1) $tts = New-Object βTypeName System.Speech.Synthesis.SpeechSynthesizer $tts.Speak('Hello, World!') # List available voices Foreach ($voice in $SpeechSynthesizer.GetInstalledVoices()){ $Voice.VoiceInfo | Select-Object Gender, Name, Culture, Description } # Change voice $tts.SelectVoice("Microsoft Zira Desktop") $tts.Speak('Hello, World!') # Save output $WavFileOut = Join-Path -Path $env:USERPROFILE -ChildPath "Desktop\thinkpowershell-demo.wav" # (2) $SpeechSynthesizer.SetOutputToWaveFile($WavFileOut)
VHDX file
Create a new 256 GB dynamic VHDX file, mount it, initialize it, and create and format the partition [Zacker][Zacker]: 91
New-VHD -Path C:\Data\disk1.vhdx -SizeBytes 256GB -Dynamic |
Mount-VHD -Passthru |
Initialize-Disk -PassThru |
New-Partition -DriveLetter X -UseMaximumSize |
Format-Volume -Filesystem ntfs -FileSystemLabel data1 -Confirm:$False -Force
Restart Wi-Fi adapter
$adaptor = Get-WmiObject -Class Win32_NetworkAdapter | Where-Object {$_.Name -like "*Wireless*"}
$adaptor.Disable()
$adaptor.Enable()
Add a member to a group
Add-ADGroupMember -Identity $group -Members $user1,$user2
Add a new local admin
nlu ansible
Add-LocalGroupMember Administrators ansible
Create a virtual switch with SET enabled
Create a virtual switch with SET enabled. [Zacker][Zacker]: 254
New-VMSwitch -Name SETSwitch -NetAdapterName "nic1","nic2" -EnableEmbeddedTeaming $true
Add-VMNetworkAdapter -VMName server1 -SwitchName setswitch -Name set1
Get-
][Get-NetAdapterRdma] and [Enable-NetAdapterRdma
][Enable-NetAdapterRdma].
Implement nested virtualization
Both the physical host and the nested virtual host must be running Windows Server 2016, but before installing Hyper-V on the nested host, the following configurations must be made. [Zacker][Zacker]: 181
Provide nested host's processor with access to virtualization technology on the physical host
Set-VMProcessor -VMName server1 -ExposeVirtualizationExtensions $true
Set-VMMemory -VMName SRV01 -DynamicMemoryEnabled $false
Set-VMProcessor -VMName SVR01 -Count 2
Set-VMNetworkAdapter -VMName SVR01 -Name "NetworkAdapter" -MACAddressSpoofing On
Enable CredSSP
On the remote (managed) server [Zacker][Zacker]: 176
Enable-PSRemoting
Enable-WSManCredSSP
Set-Item WSMan:\localhost\client\trustedhosts -Value "hypervserver.domain.com"
Enable-WSManCredSSP -Role client -DelegateComputer "hypervserver.domain.com"
Configure Server Core
Manually configure network interface, if a DHCP server is unavailable [Zacker][Zacker]: 19
New-NetIPAddress 10.0.0.3 -InterfaceAlias "Ethernet' -PrefixLength 24
Set-DnsClientServerAddress -InterfaceIndex 6 -ServerAddresses ("192.168.0.1","192.168.0.2")
Add-Computer -DomainName adatum.com -NewName Server8 -Credential adatum\administrator
Update Server Core image
Mount-WindowsImage -ImagePath .\CoreServer.vhdx -Path .\MountDir -Index 1
Add-WindowsPackage -Path .\MountDir -PackagePath C:\ServicingPackages_cabs
Dismount-WindowsImage -Path .\MountDir -Save
Implement DDA
Discrete Device Assignment (DDA) begins with finding the Instance ID of the device needed to be passed through. [Zacker][Zacker]: 212
Get-PnpDevice -PresentOnly
Disable-PnpDevice -InstanceId # Remove host-installed drivers
Get-PnpDeviceProperty # Provide `InstanceId` and `KeyName` values in order to get value for `LocationPath` parameter in next command
Dismount-VmHostAssignableDevice -LocationPath # Remove the device from host control
Add-VMAssignableDevice -VM -LocationPath # Attach the device to a guest
Configure live migration
Live migration is possible between Hyper-V hosts that are not clustered, but they must be within the same (or trusted) domains. [Zacker][Zacker]: 306
Enable-VMMigration
Set-VMMigrationNetwork 192.168.4.0
Set-VMHost -VirtualMachineMigrationAuthenticationType Kerberos
Set-VMHost -VirtualMachineMigrationPerformanceOption smbtransport
Configure S2D cluster
New-Cluster -Name cluster1 -node server1,server2,server3,server4 -NoStorage
Enable-ClusterStorageSpacesDirect
Install Docker Enterprise
[Zacker][Zacker]: 266
Install-Module -Name dockermsftprovider -repository psgallery -force
Install-Package -Name docker -ProviderName dockermsftprovider
Handle XML files
Find a sample XML file here
Assign the output of [gc
][Get-Content] to a variable
[xml]$xdoc = gc $xmlfile
$xdoc.catalog.book | Format-Table -Autosize
$xdoc.catalog.book[0]
SelectNodes
and SelectSingleNode
methods.
$xdoc.SelectNodes('//author')
This produces the same output as the command above (in XPath nodes are 1-indexed).
```powershell
$xdoc.SelectSingleNode('//book[1]')
Select-Xml
][Select-Xml] wraps the returned XML node with additional metadata, including the pattern searched.
However, it can accept piped input.
(Select-Xml -Xml $xdoc -Xpath '//book[1]').Node
($xml | Select-Xml -Xpath '//book[1]').Node
Update Server Core images
[MeasureUp Lab][pl:70-740]
Mount-WindowsImage -ImagePath .\CoreServer.vhdx -Path .\MountDir -Index 1
Add-WindowsPackage -Path .\MountDir PackagePath C:\ServicingPackages_cabs
Dismount-WindowsImage -Path .\MountDir -Save
Pass-through disk
[Zacker][Zacker]: 226
Set-Disk -Number 2 -IsOffline $true
Add-VMHardDiskDrive -VMName server1 -ControllerType scsi -DiskNumber 2
Site-aware failover cluster
Configure failover clusters for two offices [Zacker][Zacker]: 366
New-ClusterFaultDomain -Name ny -Type site -Description "Primary" -Location "New York, NY"
New-ClusterFaultDomain -Name sf -Type site -Description "Secondary" -Location "San Francisco, CA"
Set-ClusterFaultDomain -Name node1 -Parent ny
Set-ClusterFaultDomain -Name node2 -Parent ny
Set-ClusterFaultDomain -Name node3 -Parent sf
Set-ClusterFaultDomain -Name node4 -Parent sf
Filter AD account information
Get-aduser -filter {(SamAccountName -like "*CA0*")} -properties Displayname, SaMaccountName, Enabled, EmailAddress, proxyaddresses |
Where {($_.EmailAddress -notlike "*@*")} |
Where {($_.Enabled -eq $True)} |
Select Displayname, SaMaccountName, Enabled, EmailAddress, @{L=βProxyAddress_1'; E={$_.proxyaddresses[0]}}, @{L=βProxyAddress_2'; E={$_.ProxyAddresses[1]}} |
Export-csv .\usersnoemail2.csv -notypeinformation
Create VM with installation media
[Practice Lab][pl:70-740]
New-VM PLABWIN102 1536mb 1 -SwitchName 'Private network 1' -NewVHDPath 'C:\Users\Public\Documents\Hyper-V\Virtual hard disks\PLABWIN102.vhdx' -NewVHDSizeBytes 127gb
Set-VMDvdDrive -VMName PLABWIN102 -Path C:\Users\Administrator.PRACTICELABS\Documents\Eval81.iso
Registry
Description | Affected key |
---|---|
Fix Windows Search bar docs.microsoft.com | HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search |
Remove 3D Objects howtogeek.com | HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace |
Display seconds in system clock howtogeek.com | HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced |
Disable Aero Shake howtogeek.com | HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced |
Remove 3D Objects from This PC howtogeek.com
Remove-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{0DB7E03F-FC29-4DC6-9020-FF41B59E513A}'
New-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name ShowSecondsInSystemClock -Value 1
Restart-Computer
New-Item -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name BingSearchEnabled -Value 0
New-Item -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name CortanaConsent -Value 0
Start-Transaction
][Start-Transaction] and [Complete-Transaction
][Complete-Transaction] [Holmes][Holmes]: 604
Start-Transaction
New-Item TempKey -UseTransaction
Complete-Transaction
Set-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name EnableLUA -Value 0
WinForms
# Load required assemblies
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
# Drawing form and controls
$Form_HelloWorld = New-Object System.Windows.Forms.Form
$Form_HelloWorld.Text = "Hello World"
$Form_HelloWorld.Size = New-Object System.Drawing.Size(272,160)
$Form_HelloWorld.FormBorderStyle = "FixedDialog"
$Form_HelloWorld.TopMost = $true
$Form_HelloWorld.MaximizeBox = $false
$Form_HelloWorld.MinimizeBox = $false
$Form_HelloWorld.ControlBox = $true
$Form_HelloWorld.StartPosition = "CenterScreen"
$Form_HelloWorld.Font = "Segoe UI"
# adding a label to my form
$label_HelloWorld = New-Object System.Windows.Forms.Label
$label_HelloWorld.Location = New-Object System.Drawing.Size(8,8)
$label_HelloWorld.Size = New-Object System.Drawing.Size(240,32)
$label_HelloWorld.TextAlign = "MiddleCenter"
$label_HelloWorld.Text = "Hello World"
$Form_HelloWorld.Controls.Add($label_HelloWorld)
# add a button
$button_ClickMe = New-Object System.Windows.Forms.Button
$button_ClickMe.Location = New-Object System.Drawing.Size(8,80)
$button_ClickMe.Size = New-Object System.Drawing.Size(240,32)
$button_ClickMe.TextAlign = "MiddleCenter"
$button_ClickMe.Text = "Click Me!"
$button_ClickMe.Add_Click({
$button_ClickMe.Text = "You did click me!"
Start-Process calc.exe
})
$Form_HelloWorld.Controls.Add($button_ClickMe)
# show form
$Form_HelloWorld.Add_Shown({$Form_HelloWorld.Activate()})
[void] $Form_HelloWorld.ShowDialog()
Modules
Create a new module by placing a .psm1 file in a directory of the same name
.\Starship\Starship.psm1
Import-Module
][Import-Module] (execution policy must allow this).
ipmo .\Starship
Using module .\Starship
Sample enumeration
Add-Type -AssemblyName System.Drawing
$count = [Enum]::GetValues([System.Drawing.KnownColor]).Count
[System.Drawing.KnownColor](Get-Random -Minimum 1 -Maximum $count)
Migrate a VM
Enable-VMMigration
Set-VMMigrationNetwork 192.168.10.1
Set-VMHost -VirtualMachineMigrationAuthenticationType Kerberos
Set-VMHost -VirtualMachineMigrationPerformanceOption SMBTransport
Storage Spaces Direct
[MeasureUp][mu:70-740]
New-Cluster -Name HC-CLU1 -Node node1, node2, node3, node4 -NoStorage
Enable-ClusterStorageSpacesDirect -CacheMode Disabled -AutoConfig:0 -SkipEligibilityChecks
New-StoragePool -StorageSubSystemFriendlyName *Cluster* -FriendlyName S2DPool -ProvisioningTypeDefault Fixed -PhysicalDisk (Get-PhysicalDisk | Where-Object -Property CanPool -eq $true)
$pool = Get-StoragePool S2DPool
New-StorageTier -StoragePoolUniqueID ($pool).UniqueID -FriendlyName Performance -MediaType HDD -ResiliencySettingName Mirror
New-StorageTier -StoragePoolUniqueID ($pool).UniqueID -FriendlyName Capacity -MediaType HDD -ResiliencySettingName Parity
New-Volume -StoragePool $pool -FriendlyName SharedVol1 -FileSystem CSVFS_REFS -StorageTiersFriendlyNames Performance, Capacity -StorageTierSizes 2GB, 10GB
Scheduled task
Automatically run SSH server in WSL on system start
$action = New-ScheduledTaskAction -Execute C:\WINDOWS\System32\bash.exe -Argument '-c sudo service ssh start'
$trigger = New-ScheduledTaskTrigger -AtLogon
Register-ScheduledTask -TaskName 'SSH server' -Trigger $trigger -Action $action
Network connection alert
Cmdlets
Select-String
Select-String -Path *.yml -Pattern 'ansible_host' -Context 2,3