├── chapter_13.ps1 ├── chapter_18.ps1 ├── chapter_4.ps1 ├── chapter_16.ps1 ├── chapter_19.ps1 ├── chapter_11.ps1 ├── chapter_12.ps1 ├── chapter_9.ps1 ├── chapter_10.ps1 ├── chapter_8.ps1 ├── chapter_7.ps1 ├── chapter_5.ps1 ├── chapter_14.ps1 ├── chapter_6.ps1 └── chapter_15.ps1 /chapter_13.ps1: -------------------------------------------------------------------------------- 1 | #region Use Native PowerShell Where Possible 2 | 3 | $text = [System.IO.File]::ReadAllText('C:\textfile.txt') 4 | ## Do some stuff with the string in $text here 5 | 6 | $text = Get-Content -Path 'C:\textfile.txt' -Raw 7 | ## Do some stuff with the string in $text here 8 | 9 | #endregion -------------------------------------------------------------------------------- /chapter_18.ps1: -------------------------------------------------------------------------------- 1 | #region Learn the Pester Basics 2 | 3 | describe 'My computer tests' { 4 | it 'my computer name is what I expect it to be' { 5 | $env:COMPUTERNAME | should be 'PSGOD' 6 | } 7 | } 8 | 9 | Invoke-Pester -Path '~\Tests\mycomputer.tests.ps1' 10 | 11 | #endregion 12 | 13 | #region Use PSScriptAnalyzer 14 | 15 | Function Gather-OperatingSystem { 16 | [CmdletBinding()] 17 | Param ( 18 | [Parameter(Mandatory)] 19 | $ComputerName = $env:COMPUTERNAME 20 | ) 21 | Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName 22 | } 23 | Gather-OperatingSystem 24 | 25 | Invoke-ScriptAnalyzer -Path ~\PathToScript\Gather-OperatingSystem.ps1 26 | 27 | #endregion -------------------------------------------------------------------------------- /chapter_4.ps1: -------------------------------------------------------------------------------- 1 | #region Write Comments Before Coding 2 | 3 | ## Find the file here 4 | ## Set the file's ACL to read/write for all users 5 | ## Read the file and parse the computer name from it 6 | ## Attempt to connect to the computer and if successful, find the service 7 | 8 | #region Find the file here 9 | #endregion 10 | #region Set the file's ACL to read/write for all users 11 | #endregion 12 | #region Read the file and parse the computer name from it 13 | #endregion 14 | #region Attempt to connect to the computer and if successful, find the service 15 | #endregion 16 | 17 | #endregion 18 | 19 | #region Use Your Code As a Todo List 20 | ## Run some program 21 | & C:\Program.exe 22 | ## Find the log file it generates and send it back to me 23 | Get-ChildItem -Path 'C:\Program Files\Program\Logs' -Filter '*.log' | Get-Content 24 | 25 | ## TODO: This needs to run with Start-Process. More control that way 26 | ## Run some program 27 | & C:\Program.exe 28 | ## TODO: Sometimes this program creates a .txt file as the log. Need to account for this edge case. 29 | ## Find the log file it generates and send it back to me 30 | Get-ChildItem -Path 'C:\Program Files\Program\Logs' -Filter '*.log' | Get-Content 31 | #endregion -------------------------------------------------------------------------------- /chapter_16.ps1: -------------------------------------------------------------------------------- 1 | #region Don’t Use the Pipeline 2 | 3 | $array = 0..1000000 4 | 5 | Measure-Command -Expression {$null = $array.foreach({$_})} 6 | 7 | Measure-Command -Expression {$array | foreach-object { $_ }} 8 | 9 | #endregion 10 | 11 | #region Use the foreach Statement in PowerShell Core 12 | 13 | $array = 0..1000000 14 | Measure-Command -Expression { $null = foreach ($item in $array) { $item }} 15 | Measure-Command -Expression { $null = $array.foreach({$_})} 16 | 17 | #endregion 18 | 19 | #region Use Parallel Processing 20 | 21 | ## servers.txt 22 | SRV1 23 | SRV2 24 | SRV3 25 | 26 | $servers = Get-Content -Path C:\Servers.txt 27 | Invoke-Command -ComputerName $servers -ScriptBlock { 28 | ## Do something on the server here 29 | } 30 | 31 | $servers = Get-Content -Path C:\Servers.txt 32 | Invoke-Command -ComputerName $servers -AsJob -ScriptBlock { 33 | ## Do something on the server here 34 | } 35 | 36 | #endregion 37 | 38 | #region Use the .NET StreamReader Class When Reading Large Text Files 39 | 40 | Get-Content -Path C:\MyHugeFile.txt 41 | 42 | $sr = New-Object -Type System.IO.StreamReader -ArgumentList 'C:\MyHugeFile.txt' 43 | while ($sr.Peek() -ge 0) { 44 | $sr.ReadLine() 45 | } 46 | 47 | #endregion -------------------------------------------------------------------------------- /chapter_19.ps1: -------------------------------------------------------------------------------- 1 | #region Write for Cross-Platform 2 | 3 | ls -Path 'C:\SomeFolder' | ForEach-Object { $_.Encrypt() } 4 | 5 | #endregion 6 | 7 | #region Don’t Query the Win32_Product CIM Class 8 | 9 | Get-CimInstance -Class Win32_Product 10 | 11 | #endregion 12 | 13 | #region Store “Formattable” Strings for Later Use 14 | 15 | 'Hi, I am {0} {1}' -f 'Adam','Bertram' 16 | 17 | $FString = "SELECT * FROM some.dbo WHERE Hostname = '{0}'" 18 | "comp1","comp2" | ForEach-Object { 19 | Invoke-SQLcmd -Query ($FString -f $_) 20 | } 21 | 22 | #endregion 23 | 24 | #region Use Out-GridView for GUI-Based Sorting and Filtering 25 | 26 | Get-Service | Out-GridView 27 | 28 | #endregion 29 | 30 | #region Don’t Make Automation Scripts Interactive 31 | 32 | function Do-Thing { 33 | [CmdletBinding()] 34 | param( 35 | [Parameter(Mandatory)] 36 | [string]$Foo 37 | ) 38 | 39 | } 40 | 41 | Do-Thing 42 | 43 | function Do-Thing { 44 | [CmdletBinding()] 45 | param( 46 | [Parameter()] 47 | [string]$Foo 48 | ) 49 | $thing = Read-Host -Prompt 'I need some input!' 50 | ## Do stuff with $thing 51 | 52 | } 53 | 54 | Do-Thing 55 | 56 | $credential = Get-Credential -Message 'Needs some love here' 57 | 58 | #endregion -------------------------------------------------------------------------------- /chapter_11.ps1: -------------------------------------------------------------------------------- 1 | #region Force Hard-Terminating Errors 2 | 3 | $someFile = Get-Item -Path 'a file that does not exist' 4 | $acl = Get-Acl -Path $someFile.FullName 5 | Set-Acl -Path 'a file that exists' -AclObject $acl 6 | 7 | $someFile = Get-Item -Path 'a file that does not exist' -ErrorAction Stop 8 | $acl = Get-Acl -Path $someFile.FullName 9 | Set-Acl -Path 'a file that exists' -AclObject $acl 10 | 11 | try { 12 | $someFile = Get-Item -Path 'a file that does not exist' -ErrorAction Stop 13 | $acl = Get-Acl -Path $someFile.FullName 14 | Set-Acl -Path 'a file that exists' -AclObject $acl 15 | } catch { 16 | Write-Warning -Message $_.Exception.Message 17 | } 18 | 19 | $ErrorActionPreference = 'Stop' 20 | try { 21 | $someFile = Get-Item -Path 'a file that does not exist' 22 | $acl = Get-Acl -Path $someFile.FullName 23 | Set-Acl -Path 'a file that exists' -AclObject $acl 24 | } catch { 25 | Write-Warning -Message $_.Exception.Message 26 | } 27 | 28 | #endregion 29 | 30 | #region Avoid Using $? 31 | 32 | $someFile = Get-Item -Path 'a file that does not exist' -ErrorAction Ignore 33 | if (-not $?) { 34 | Write-Warning -Message $_.Exception.Message 35 | } else { 36 | $acl = Get-Acl -Path $someFile.FullName 37 | Set-Acl -Path 'a file that exists' -AclObject $acl 38 | } 39 | 40 | #endregion -------------------------------------------------------------------------------- /chapter_12.ps1: -------------------------------------------------------------------------------- 1 | #region Sign Scripts 2 | 3 | New-SelfSignedCertificate -DnsName -CertStoreLocation Cert:\CurrentUser\My -Type Codesigning 4 | 5 | Export-Certificate -FilePath codesigning.cer -Cert Cert:\CurrentUser\My\ 6 | Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root\ -FilePath .\codesigning.cer 7 | Import-Certificate -CertStoreLocation Cert:\LocalMachine\TrustedPublisher\ -FilePath .\codesigning.cer 8 | 9 | ## This assumes there is only one code signing cert in the store 10 | $cert = Get-ChildItem -Path Cert:\CurrentUser\My\ -CodeSigningCert 11 | Set-AuthenticodeSignature -FilePath C:\Temp\script1.ps1 -Certificate $cert 12 | 13 | #endregion 14 | 15 | #region Never Store Sensitive Information in Clear Text in Code 16 | 17 | $password = ConvertTo-SecureString 'MySecretPassword' -AsPlainText -Force 18 | $credential = New-Object System.Management.Automation.PSCredential ('root', $password) 19 | 20 | $credential = Get-Credential 21 | 22 | $credential | Export-CliXml -Path .\credential.xml 23 | 24 | #endregion 25 | 26 | #region Don’t Use Invoke-Expression 27 | 28 | [CmdletBinding()] 29 | param( 30 | [Parameter()] 31 | [string]$FormInput 32 | ) 33 | ## Do something with $FormInput 34 | 35 | [CmdletBinding()] 36 | param( 37 | [Parameter()] 38 | [string]$FormInput 39 | ) 40 | Invoke-Expression -Command $FormInput 41 | 42 | Remove-Item -Path 'C:\' -Recurse -Force 43 | 44 | #endregion 45 | 46 | #region Use PowerShell Constrained Language Mode 47 | 48 | $ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage" 49 | 50 | #endregion -------------------------------------------------------------------------------- /chapter_9.ps1: -------------------------------------------------------------------------------- 1 | #region Explicitly Type All Parameters 2 | 3 | function Get-Thing { 4 | param( 5 | $Param 6 | ) 7 | } 8 | 9 | function Get-Thing { 10 | param( 11 | $Param 12 | ) 13 | Write-Host "The property Foo's value is [$($Param.Foo)]" 14 | } 15 | 16 | function Get-Thing { 17 | param( 18 | [pscustomobject]$Param 19 | ) 20 | Write-Host "The property Foo's value is [$($Param.Foo)]" 21 | } 22 | 23 | #endregion 24 | 25 | #region Always Use Parameter Validation When Possible 26 | 27 | $departments = 'ACCT', 'HR', 'ENG' 28 | 29 | function New-ServerName { 30 | param( 31 | [Parameter()] 32 | [string]$Department 33 | ) 34 | "$Department-$(Get-Random -Maximum 99)" 35 | } 36 | 37 | function New-ServerName { 38 | param( 39 | [Parameter()] 40 | [ValidateSet('ACCT','HR','ENG')] 41 | [string]$Department 42 | ) 43 | "$Department-$(Get-Random -Maximum 99)" 44 | } 45 | 46 | New-ServerName -Department 'Software Engineering' 47 | 48 | #endregion 49 | 50 | #region Always Define a Function’s OutputType 51 | 52 | function Get-File { 53 | param( 54 | [Parameter()] 55 | [string]$FilePath 56 | ) 57 | Get-Item -Path $FilePath 58 | } 59 | 60 | $file = Get-File -FilePath C:\Test-PendingReboot.ps1 61 | 62 | Get-File -Path 'C:\myfile.txt' | Get-Member 63 | 64 | function Get-File { 65 | [OutputType([System.String])] 66 | param( 67 | [Parameter()] 68 | [string]$FilePath 69 | ) 70 | Get-Item -Path $FilePath 71 | } 72 | 73 | #endregion 74 | 75 | #region Write Specific Regular Expressions 76 | 77 | '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' 78 | 79 | '^.{8}-.{4}-.{4}-.{4}-.{12}$' 80 | 81 | 'michael' -match 'Michael' 82 | 83 | 'michael' -cmatch 'Michael' 84 | 85 | 86 | #endregion -------------------------------------------------------------------------------- /chapter_10.ps1: -------------------------------------------------------------------------------- 1 | #region Give Your Variables Meaningful Names 2 | 3 | $t = 'servers.txt' 4 | $x = Get-Content -Path $t 5 | foreach ($r in $x) { 6 | Invoke-Command -ComputerName $r -ScriptBlock {'Doing something on this computer'} 7 | } 8 | 9 | $serversFilePath = 'servers.txt' 10 | $serverNames = Get-Content -Path $serversFilePath 11 | foreach ($serverName in $serverNames) { 12 | Invoke-Command -ComputerName $serverName -ScriptBlock {'Doing something on this computer'} 13 | } 14 | 15 | #endregion 16 | 17 | #region String Substitution 18 | 19 | $departments = @( 20 | 'HR' 21 | 'ACCT' 22 | 'ENG' 23 | ) 24 | 25 | foreach ($dept in $departments) { 26 | $srvName = "$dept-SRV" 27 | $srvName 28 | } 29 | 30 | foreach ($dept in $departments) { 31 | $srvName = '{0}-SRV' -f $dept 32 | $srvName 33 | } 34 | 35 | #endregion 36 | 37 | #region Write Comment-Based Help 38 | 39 | function Get-ConfigurationFile { 40 | <# 41 | .SYNOPSIS 42 | Finds the configuration file for the Acme application. 43 | .DESCRIPTION 44 | This function attempts to find the configuration file for Acme application 45 | based on the path provided. If found, it then returns a file object to then 46 | pass to other functions to manipulate the configuration file. 47 | .EXAMPLE 48 | PS C:\> Get-ConfigurationFile -FilePath 'C:\Program Files\AcmeApp\config.xml' 49 | This example will look for the configuration file at C:\Program Files\AcmeApp\config.xml 50 | and, if found, will return a file object. 51 | .OUTPUTS 52 | System.IO.FileInfo 53 | #> 54 | param( 55 | [Parameter()] 56 | [string]$FilePath 57 | ) 58 | ## Some code here that looks for the configuration file 59 | } 60 | 61 | #endregion 62 | 63 | #region Weigh the Difference Between Performance and Readability 64 | 65 | $array = 0..100 66 | 67 | $array += 101 68 | 69 | $arrayList = [System.Collections.ArrayList](0..100) 70 | $arrayList.Add(101) 71 | 72 | 73 | 74 | 75 | #endregion -------------------------------------------------------------------------------- /chapter_8.ps1: -------------------------------------------------------------------------------- 1 | #region DRY: Don’t Repeat Yourself 2 | 3 | if (-not (Get-WindowsFeature -Name 'Web-Server').Installed) { 4 | Install-WindowsFeature -Name 'Web-Server' 5 | } 6 | 7 | if (-not (Get-WindowsFeature -Name 'Web-Server').Installed) { 8 | Install-WindowsFeature -Name 'Web-Server' 9 | } 10 | if (-not (Get-WindowsFeature -Name 'SNMP-Server').Installed) { 11 | Install-WindowsFeature -Name 'Web-Server' 12 | } 13 | if (-not (Get-WindowsFeature -Name 'SNMP-Services').Installed) { 14 | Install-WindowsFeature -Name 'Web-Server' 15 | } 16 | if (-not (Get-WindowsFeature -Name 'Backup-Features').Installed) { 17 | Install-WindowsFeature -Name 'Web-Server' 18 | } 19 | if (-not (Get-WindowsFeature -Name 'XPS-Viewer').Installed) { 20 | Install-WindowsFeature -Name 'Web-Server' 21 | } 22 | if (-not (Get-WindowsFeature -Name 'Wireless-Networking').Installed) { 23 | Install-WindowsFeature -Name 'Web-Server' 24 | } 25 | 26 | $featureNames = @( 27 | 'Web-Server' 28 | 'SNMP-Server' 29 | 'SNMP-Services' 30 | 'Backup-Features' 31 | 'XPS-Viewer' 32 | 'Wireless-Networking' 33 | ) 34 | foreach ($name in $featureNames) { 35 | if (-not (Get-WindowsFeature -Name $name).Installed) { 36 | Install-WindowsFeature -Name $name 37 | } 38 | } 39 | 40 | #endregion 41 | 42 | #region Don’t Store Configuration Items in Code 43 | 44 | @{ 45 | 'WindowsFeatures' = @( 46 | 'Web-Server' 47 | 'SNMP-Server' 48 | 'SNMP-Services' 49 | 'Backup-Features' 50 | 'XPS-Viewer' 51 | 'Wireless-Networking' 52 | ) 53 | } 54 | 55 | $configuration = Import-PowerShellDataFile "$PSScriptRoot\ScriptConfiguration.psd1" 56 | foreach ($name in $configuration.WindowsFeatures) { 57 | if (-not (Get-WindowsFeature -Name $name).Installed) { 58 | Install-WindowsFeature -Name $name 59 | } 60 | } 61 | 62 | #endregion 63 | 64 | #region Always Remove Dead Code 65 | 66 | [CmdletBinding()] 67 | param( 68 | [Parameter(Mandatory)] 69 | [string]$ComputerName, 70 | [Parameter()] 71 | [pscredential]$Credential 72 | ) 73 | $somelostForgottenVariable = 'bar' 74 | $service = Get-Service -Name 'foo' -ComputerName $ComputerName 75 | if ($service.Status -eq 'Running') { 76 | Write-Host 'Do something here' 77 | } elseif ($someLostForgottenVariable -eq 'baz') { 78 | Write-Host 'do something else here' 79 | } 80 | 81 | 82 | 83 | #endregion -------------------------------------------------------------------------------- /chapter_7.ps1: -------------------------------------------------------------------------------- 1 | #region Use a Logging Function 2 | 3 | Add-Content -Path 'C:\activity.log' -Value "Doing something here" 4 | 5 | function Write-Log { 6 | [CmdletBinding()] 7 | <# 8 | .SYNOPSIS 9 | This function creates or appends a line to a log file 10 | 11 | .DESCRIPTION 12 | This function writes a line to a log file 13 | 14 | .PARAMETER Message 15 | The message parameter is the log message you'd like to record to the log file. 16 | 17 | .PARAMETER LogLevel 18 | The logging level is the severity rating for the message you're recording. 19 | You have 3 severity levels available; 1, 2 and 3 from informational messages 20 | for FYI to critical messages. This defaults to 1. 21 | 22 | .EXAMPLE 23 | PS C:\> Write-Log -Message 'Valuell -LogLevel "Value2' 24 | 25 | This example shows how to call the Write-Log function with named parameters. 26 | #> 27 | param ( 28 | [Parameter(Mandatory)] 29 | [ValidateNotNullOrEmpty()] 30 | [string]$Message, 31 | 32 | [Parameter()] 33 | [ValidateNotNullOrEmpty()] 34 | [string]$FilePath = "$PSScriptRoot\activity.log", 35 | 36 | [Parameter()] 37 | [ValidateSet(1, 2, 3)] 38 | [int]$LogLevel = 1 39 | ) 40 | [pscustomobject]@{ 41 | 'Time' = (Get-Date -Format 'MM-dd-yy HH:mm:sstt') 42 | 'Message' = $Message 43 | 'Source' = "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)" 44 | 'Severity' = $LogLevel 45 | } | Export-Csv -Path $FilePath -Append -NoTypeInformation 46 | } 47 | 48 | #endregion 49 | 50 | #region Clean Up Verbose Messages 51 | 52 | [CmdletBinding()] 53 | param( 54 | [Parameter()] 55 | [string]$ComputerName, 56 | [Parameter()] 57 | [string]$FilePath 58 | ) 59 | Get-Content -Path "\\$ComputerName\$FilePath" 60 | 61 | ## read-file.ps1 62 | [CmdletBinding()] 63 | param( 64 | [Parameter()] 65 | [string]$ComputerName, 66 | [Parameter()] 67 | [string]$FilePath 68 | ) 69 | Write-Verbose -Message "Attempting to read file at [\\$ComputerName\$FilePath]..." 70 | Get-Content -Path "\\$ComputerName\$FilePath" 71 | 72 | ./read-file.ps1 -ComputerName FOO -FilePath 'bar.txt' -Verbose 73 | 74 | 75 | 76 | #endregion -------------------------------------------------------------------------------- /chapter_5.ps1: -------------------------------------------------------------------------------- 1 | #region Write Functions with One, Single Goal 2 | 3 | function Copy-ConfigurationFile { 4 | [CmdletBinding()] 5 | param( 6 | [Parameter(Mandatory)] 7 | [string]$FilePath, 8 | [Parameter(Mandatory)] 9 | [string[]]$ServerName 10 | ) 11 | foreach ($server in $ServerName) { 12 | if (Test-Connection -ComputerName $server -Quiet -Count 1) { 13 | Copy-Item -Path $FilePath -Destination "\\$server\c$" 14 | } else { 15 | Write-Warning "The server [$server] is offline. Unable to copy file." 16 | } 17 | } 18 | } 19 | 20 | 21 | #endregion 22 | 23 | #region Build Functions with Pipeline Support 24 | function Start-AppBackup { 25 | param( 26 | [Parameter()] 27 | [string]$ComputerName, 28 | [Parameter()] 29 | [string]$BackupLocation = '\\MYBACKUPSERVER\Backups' 30 | ) 31 | Copy-Item -Path "\\$ComputerName\c$\Program Files\SomeApp\Database\*" -Destination "$BackupLocation\$ComputerName" 32 | } 33 | function Restore-AppBackup { 34 | param( 35 | [Parameter(Mandatory)] 36 | [string]$ComputerName, 37 | [Parameter()] 38 | [string]$BackupLocation = '\\MYBACKUPSERVER\Backups' 39 | ) 40 | Copy-Item -Path "$BackupLocation\$ComputerName\*" -Destination "\\$ComputerName\c$\Program Files\SomeApp\Database" 41 | } 42 | 43 | Start-AppBackup -ComputerName FOO 44 | Restore-AppBackup -ComputerName FOO 45 | 46 | function Start-AppBackup { 47 | param( 48 | [Parameter()] 49 | [string]$ComputerName, 50 | [Parameter()] 51 | [string]$BackupLocation = '\\MYBACKUPSERVER\Backups' 52 | ) 53 | Copy-Item -Path "\\$ComputerName\c$\Program Files\SomeApp\Database\*" -Destination "$BackupLocation\$ComputerName" 54 | } 55 | function Get-AppBackup { 56 | param( 57 | [Parameter(Mandatory)] 58 | [string]$ComputerName, 59 | [Parameter()] 60 | [string]$BackupLocation = '\\MYBACKUPSERVER\Backups' 61 | ) 62 | [pscustomobject]@{ 63 | 'ComputerName' = $ComputerName 64 | 'Backup' = (Get-ChildItem -Path "\\$BackupLocation\$ComputerName") 65 | } 66 | } 67 | function Restore-AppBackup { 68 | param( 69 | [Parameter(Mandatory,ValueFromPipelineByPropertyName)] 70 | [string]$ComputerName, 71 | [Parameter()] 72 | [string]$BackupLocation = '\\MYBACKUPSERVER\Backups' 73 | ) 74 | Copy-Item -Path "$BackupLocation\$ComputerName\*" -Destination "\\$ComputerName\c$\Program Files\SomeApp\Database" 75 | } 76 | 77 | Get-AppBackup -ComputerName FOO | Restore-AppBackup 78 | #endregion 79 | 80 | #region Save Commonly Used, Interactive Functions to Your User Profile 81 | function prompt { 'PS>' } 82 | 83 | $Profile 84 | #endregion -------------------------------------------------------------------------------- /chapter_14.ps1: -------------------------------------------------------------------------------- 1 | #region Think Ahead and Build Abstraction “Layers” 2 | 3 | #requires -Module HyperV 4 | [CmdletBinding()] 5 | param( 6 | [Parameter(Mandatory)] 7 | [string]$VmName 8 | ) 9 | ## Use the Start-Vm cmdlet in the HyperV module to start a VM 10 | Start-Vm -Name $VmName 11 | ## Do some stuff to the VM here 12 | ## Use the Stop-Vm cmdlet in the HyperV module to stop the VM 13 | Stop-Vm -Name $VmName 14 | 15 | #requires -Module HyperV, VmWare 16 | [CmdletBinding()] 17 | param( 18 | [Parameter(Mandatory)] 19 | [string]$VmName, 20 | [Parameter()] 21 | [ValidateSet('HyperV','VmWare')] 22 | [string]$Hypervisor 23 | ) 24 | switch ($Hypervisor) { 25 | 'HyperV' { 26 | ## Use the Start-Vm cmdlet in the HyperV module to start a VM 27 | Start-Vm -Name $VmName 28 | ## Do some stuff to the VM here 29 | ## Use the Stop-Vm cmdlet in the HyperV module to stop the VM 30 | Stop-Vm -Name $VmName 31 | break 32 | } 33 | 'VmWare' { 34 | ## Use whatever command the VmWare module has in it to start the VM 35 | Start-VmWareVm -Name $VmName 36 | ## do stuff to the VmWare VM 37 | ## Use whatever command the VmWare module has in it to stop the VM 38 | Stop-VmWareVm -Name $VmName 39 | break 40 | } 41 | default { 42 | "The hypervisor you passed [$_] is not supported" 43 | } 44 | } 45 | 46 | #requires -Module HyperV, VmWare 47 | [CmdletBinding()] 48 | param( 49 | [Parameter(Mandatory)] 50 | [string]$VmName 51 | ) 52 | function Get-Hypervisor { 53 | ## simple helper function to determine the hypervisor 54 | param( 55 | [Parameter()] 56 | [string]$VmName 57 | ) 58 | ## Some code here to figure out what type of hypervisor this VM is running on 59 | ## Return either 'HyperV' or 'VmWare' 60 | } 61 | $hypervisor = Get-Hypervisor -VmName $VmName 62 | switch ($Hypervisor) { 63 | 'HyperV' { 64 | ## Use the Start-Vm cmdlet in the HyperV module to start a VM 65 | Start-Vm -Name $VmName 66 | ## Do some stuff to the VM here 67 | ## Use the Stop-Vm cmdlet in the HyperV module to stop the VM 68 | Stop-Vm -Name $VmName 69 | break 70 | } 71 | 'VmWare' { 72 | ## Use whatever command the VmWare module has in it to start the VM 73 | Start-VmWareVm -Name $VmName 74 | ## do stuff to the VmWare VM 75 | ## Use whatever command the VmWare module has in it to stop the VM 76 | Stop-VmWareVm -Name $VmName 77 | break 78 | } 79 | default { 80 | "The hypervisor you passed [$_] is not supported" 81 | } 82 | } 83 | 84 | #endregion 85 | 86 | #region Make Module Functions Return Common Object Types 87 | 88 | function Get-Thing { 89 | [CmdletBinding()] 90 | param() 91 | ## Do some stuff 92 | [pscustomobject]@{ 93 | 'Name' = 'XXXX' 94 | 'Type' = 'XXXX' 95 | } 96 | } 97 | function Get-OtherThing { 98 | [CmdletBinding()] 99 | param() 100 | [pscustomobject]@{ 101 | 'Name' = 'XXXX' 102 | 'Type' = 'XXXX' 103 | 'Property1' = 'Value1' 104 | 'Property2' = 'Value2' 105 | } 106 | } 107 | 108 | 109 | #endregion -------------------------------------------------------------------------------- /chapter_6.ps1: -------------------------------------------------------------------------------- 1 | #region Don’t Hardcode. Always Use Parameters 2 | 3 | ## restart-serverservice.ps1 4 | Get-Service -ComputerName FOO -Name 'service1','service2' | Restart-Service 5 | 6 | ## restart-serverservice.ps1 7 | [CmdletBinding()] 8 | param( 9 | [Parameter()] 10 | [string]$ServerName, 11 | [Parameter()] 12 | [string[]]$ServiceName 13 | ) 14 | Get-Service -ComputerName $ServerName -Name $ServiceName | Restart-Service 15 | 16 | .\restart-serverservice.ps1 -ServerName FOO -ServiceName 'service1','service2' 17 | 18 | ## restart-serverservice.ps1 19 | [CmdletBinding()] 20 | param( 21 | [Parameter()] 22 | [string]$ServerName = 'FOO', 23 | [Parameter()] 24 | [string[]]$ServiceName = @('service1','service2') 25 | ) 26 | Get-Service -ComputerName $ServerName -Name $ServiceName | Restart-Service 27 | 28 | 29 | 30 | #endregion 31 | 32 | #region Use Parameter Sets When All Parameters Should Not Be Used at Once 33 | 34 | function Reboot-Server { 35 | param( 36 | [Parameter(Mandatory)] 37 | [string]$ServerName 38 | ) 39 | if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) { 40 | Restart-Computer -ComputerName $ServerName 41 | } 42 | } 43 | 44 | Reboot-Server -ServerName FOO 45 | 46 | ## RebootServer.psm1 47 | #requires -Module ActiveDirectory 48 | function Get-Server { 49 | param( 50 | [Parameter()] 51 | [string]$ServerName 52 | ) 53 | ## If ServerName parameter was not used, pull computer names from AD 54 | if (-not ($PSBoundParameters.ContainsKey('ServerName'))) { 55 | $serverNames = Get-AdComputer -Filter * | Select-Object ​-ExpandProperty Name 56 | } else { 57 | $serverNames = $ServerName 58 | } 59 | ## send a custom object out to the pipeline for each server name found 60 | $serverNames | ForEach-Object { 61 | [pscustomobject]@{ 62 | 'ServerName' = $_ 63 | } 64 | } 65 | } 66 | 67 | ## Reboots all computers in AD. Ruh roh! 68 | Get-Server | Reboot-Server 69 | 70 | Reboot-Server -ServerName FOO 71 | 72 | ## RebootServer.psm1 73 | function Reboot-Server { 74 | param( 75 | [Parameter(Mandatory)] 76 | [string]$ServerName, 77 | [Parameter(Mandatory, ValueFromPipeline)] 78 | [pscustomobject]$InputObject 79 | ) 80 | process { 81 | if ($PSBoundParameters.ContainsKey('InputObject')) { 82 | $ServerName = $InputObject.ServerName 83 | } 84 | if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) { 85 | Restart-Computer -ComputerName $ServerName 86 | } 87 | } 88 | } 89 | 90 | ## RebootServer.psm1 91 | function Reboot-Server { 92 | param( 93 | [Parameter(Mandatory,ParameterSetName = 'ServerName')] 94 | [string]$ServerName, 95 | [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')] 96 | [pscustomobject]$InputObject 97 | ) 98 | process { 99 | if ($PSBoundParameters.ContainsKey('InputObject')) { 100 | $ServerName = $InputObject.ServerName 101 | } 102 | if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) { 103 | Restart-Computer -ComputerName $ServerName 104 | } 105 | } 106 | } 107 | #endregion 108 | 109 | #region Use a PSCredential Object Rather Than a Separate Username and Password 110 | 111 | ## somescript.ps1 112 | [CmdletBinding()] 113 | param( 114 | [Parameter()] 115 | [string]$UserName, 116 | [Parameter()] 117 | [string]$Password 118 | ) 119 | .\someapp.exe $UserName $Password 120 | 121 | .\somescript.ps1 -UserName 'adam' -Password 'MySuperS3ct!pw!' 122 | 123 | ## somescript.ps1 124 | [CmdletBinding()] 125 | param( 126 | [Parameter()] 127 | [pscredential]$Credential 128 | ) 129 | ## You'll still have to decrypt the password here but you at least keep it 130 | ## secure for as long as you can 131 | $cred = $Credential.GetNetworkCredential() 132 | .\someapp.exe $cred.UserName $cred.Password 133 | 134 | .\somescript.ps1 -Credential (Get-Credential) 135 | 136 | #endregion 137 | 138 | -------------------------------------------------------------------------------- /chapter_15.ps1: -------------------------------------------------------------------------------- 1 | #region Use Progress Bars Wisely 2 | 3 | ## Pull server computer names from AD 4 | $serverNames = Get-AdComputer -Filter '*' -SearchBase 'OU=Servers,DC=company,DC=local' | Select-Object -ExpandProperty Name 5 | foreach ($name in $serverNames) { 6 | foreach ($file in Get-ChildItem -Path "\\$name\c$\SomeFolderWithABunchOfFiles" -File) { 7 | ## Read and do stuff to each one of these files 8 | } 9 | } 10 | 11 | ## Pull server computer names from AD 12 | $serverNames = Get-AdComputer -Filter '*' -SearchBase 'OU=Servers,DC=company,DC=local' | Select-Object -ExpandProperty Name 13 | ## Process each server name 14 | for ($i=0;$i -lt $serverNames.Count;$i++) { 15 | Write-Progress -Activity 'Processing files' -Status "Server [$($serverNames[$i])]" -PercentComplete (($i / $serverNames.Count) * 100) 16 | ## Process each file on the server 17 | foreach ($file in Get-ChildItem -Path "\\$($serverNames[$i])\c$\SomeFolderWithABunchOfFiles" -File) { 18 | ## Read and do stuff to each one of these files 19 | } 20 | } 21 | 22 | #endregion 23 | 24 | #region Leave the Format Cmdlets to the Console 25 | 26 | ## readsomefiles.ps1 27 | Get-ChildItem -Path 'somepath\here' | Format-Table 28 | 29 | ## readsomefiles.ps1 30 | Get-ChildItem -Path 'somepath\here' | Format-Table | Remove-Item 31 | 32 | .\readsomefiles.ps1 | Format-Table 33 | 34 | #endregion 35 | 36 | #region Use Write-Verbose 37 | 38 | #requires -Module ActiveDirectory 39 | [CmdletBinding()] 40 | param( 41 | [Parameter(Mandatory)] 42 | [string[]]$DnsHostname, 43 | [Parameter()] 44 | [string]$DomainName = (Get-ADDomain).Forest 45 | ) 46 | $Path = "AD:\DC=$DomainName,CN=MicrosoftDNS,DC=ForestDnsZones,DC=$($DomainName.Split('.') -join ',DC=')" 47 | foreach ($Record in (Get-ChildItem -Path $Path)) { 48 | if ($DnsHostname -contains $Record.Name) { 49 | Get-Acl -Path "ActiveDirectory:://RootDSE/$($Record.DistinguishedName)" 50 | } 51 | } 52 | 53 | #requires -Module ActiveDirectory 54 | [CmdletBinding()] 55 | param( 56 | [Parameter(Mandatory)] 57 | [string[]]$DnsHostname, 58 | [Parameter()] 59 | [string]$DomainName = (Get-ADDomain).Forest 60 | ) 61 | Write-Verbose -Message 'Starting script...' 62 | $Path = "AD:\DC=$DomainName,CN=MicrosoftDNS,DC=ForestDnsZones,DC=$($DomainName.Split('.') -join ',DC=')" 63 | foreach ($Record in (Get-ChildItem -Path $Path)) { 64 | if ($DnsHostname -contains $Record.Name) { 65 | Write-Verbose -Message "Getting ACL for [$($Record.Name)]..." 66 | Get-Acl -Path "ActiveDirectory:://RootDSE/$($Record.DistinguishedName)" 67 | Write-Verbose -Message "Finished getting ACL for [$($Record.Name)]." 68 | } 69 | } 70 | Write-Verbose -Message 'Script ending...' 71 | 72 | #endregion 73 | 74 | #region Use Write-Information 75 | 76 | #requires -Module ActiveDirectory 77 | [CmdletBinding()] 78 | param( 79 | [Parameter(Mandatory)] 80 | [string[]]$DnsHostname, 81 | [Parameter()] 82 | [string]$DomainName = (Get-ADDomain).Forest 83 | ) 84 | Write-Information -MessageData 'Starting script...' 85 | $Path = "AD:\DC=$DomainName,CN=MicrosoftDNS,DC=ForestDnsZones,DC=$($DomainName.Split('.') -join ',DC=')" 86 | foreach ($Record in (Get-ChildItem -Path $Path)) { 87 | if ($DnsHostname -contains $Record.Name) { 88 | Write-Verbose -Message "Getting ACL for [$($Record.Name)]..." 89 | Get-Acl -Path "ActiveDirectory:://RootDSE/$($Record.DistinguishedName)" 90 | Write-Verbose -Message "Finished getting ACL for [$($Record.Name)]." 91 | } 92 | } 93 | Write-Information -MessageData 'Script ending...' 94 | 95 | #endregion 96 | 97 | #region Ensure a Command Returns One Type of Object 98 | 99 | $servers = ('SRV1','SRV2','SRV3','SRV4') 100 | foreach ($server in $servers) { 101 | Get-Service -ComputerName $server 102 | Get-ChildItem -Path "\\$server\c$\Users" -Directory 103 | } 104 | 105 | $servers = ('SRV1','SRV2','SRV3','SRV4') 106 | foreach ($server in $servers) { 107 | $services = Get-Service -ComputerName $server 108 | $userProfiles = Get-ChildItem -Path "\\$server\c$\Users" -Directory 109 | [pscustomobject]@{ 110 | Services = $services 111 | UserProfiles = $userProfiles 112 | } 113 | } 114 | 115 | #endregion 116 | 117 | #region Only Return Necessary Information to the Pipeline 118 | 119 | New-Item -Path 'C:\path\to\folder' -ItemType Directory 120 | 121 | $null = New-Item -Path 'C:\path\to\folder' -ItemType Directory 122 | 123 | #endregion --------------------------------------------------------------------------------