Set-ExecutionPolicy -ExecutionPolicy RemoteSigned throw/catch. Usually just throw a string: throw 'this is an error' https://en.wikipedia.org/wiki/PowerShell#Comparison_of_cmdlets_with_similar_commands These will throw (but (See this for SOME cmdlet aliases too) https://www.tutorialspoint.com/powershell https://www.red-gate.com/simple-talk/sysadmin/powershell/the-complete-guide-to-powershell-punctuation/ Reference: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/?view=powershell-6 Generally backtick ` is used for escaping instead of \. This applies for \r\n too and ". For example: $str = "one $var`r`n`$nonVar" Does not work in single-quotes! Line-continuation character: ` backtick Beware of file type handlers for *.ps1. I think default is to open the script in an editor. Watch out that the handler invokes with -File switch AND PASSES FOLLOWING PARAMS! Data Types. Awful design. [string] $var [int] $var booleans are 0 and 1 but they define $true and $false, how thoughtful. Command-line arguments. Can use $ARGS array manually, or use a 'param' block. If use 'param' block, you must specify (Mandatory=$true) or false. If true then user will be interactively prompted for missing arg values. See my example "~/code-templates/cliParamsAndRegstry.ps1" It is safe to reassign $args, like to shift with: $args = $args[1 .. ($args.length - 1)] Great article, answer #1 at https://stackoverflow.com/questions/2157554/how-to-handle-command-line-arguments-in-powershell Scripts execute fine with UNIX EOLs. "$var expansion string" Double-quotes. Seems like there is also something like '@...@' Like bash, strings can span lines (don't need ''' or """ like Java/Groovy). N.b. can't resolve even single-level members like $var.x. Therefore, use {0} formatted string to do this. Use "$(expr)" to avoid delimiter problems like Groovy's "${driveLtr}" Remember that due to PowerShell lameness you still need $ inside the () to ref variables. 'raw (static) string' Single-quotes. Therefore, just like with Bourne shell, generally use single-quoting unless string contains a single-quote/apostrophe or you need to expand variables. Bare words: Somehow, usually words with dots can be used as bare words. Slashes sometimes work but can confuse commands into thinking it's a switch. Exception: Can't do an assignment to a bare word: NO: $v = bareword Commas generally optional as param delimiter They work at end of line FOR EXTERNAL COMMANDS to continue statement onto next line. For build-in cmdlets this doesn't work! I'm not sure of criteria when this works and does not work, so be careful and test it. Example of barewords and commas: schtasks.exe /query /tn, tst.job ISE: powershell_ise Seems to break up-arrow for command history most of the time. Main benefit seems to be the command refence panel. Invoke script from CLI (case-insensitive) PowerShell 1.0: PowerShell [-NoProfile] [-C[ommand]]"& {c:\path\to\x.ps1}" PowerShell 2.0: PowerShell [-InputFormat none] [-ExecutionPolicy bypass] [-NoProfile] -F[ile] file\path.ps1 (file paths may be unqualified, unlike specified commands) PowerShell "inline command" One way to allow users on current host to execute PS scripts, as admin: powershell Set-ExecutionPolicy -ExecutionPolicy bypass -Scope LocalMachine List with: powershell Get-ExecutionPolicy [-List] (From Linux, use "pwsh" instead of "PowerShell" For some damned reason, in 2022 need to export var DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 first Unlike real PowerShell, you need to give switch -Command in order to give inline scriptlet. ) Default execution policy is Restricted (Unrestricted on Server AMIs). Get-ExecutionPolicy -list Set-ExecutionPolicy Unrestricted # Do this! If script should take no params then validate with '$args.length -ne 0' at top. Can define your own script params by adding this at top of your script: param( # Each param may specify [kindof] attrs: Parameter, Alias, Credential + many validators: # ValidateRange(0,10), ValidateSet("a","bee"), ValidatePattern('rex'), ValidateCount(3) # (Count for list params, Length is for char lengths), others for empties/nulls. [Parameter(Mandatory=$true)] [string]$pName, # If need to specify param attributes All booleans default to false, but if you specify then without value then become true, quote string vals Important param attrs: Mandatory (bool), ValueFromRemainingArguments (bool), Position (overrides auto 0-based-int), HelpMessage (str) [int]$Param2 = 3, # default value 3. N.b. default val ignore if param attr Mandatory! [switch]$Param2, [Parameter(Position=1, ValueFromRemainingArguments)] [string[]]$TheRest ) N.b. idiotic param doesn't check for extra params, so unless you are using ValueFromRemainingArguments, always check for '$args.length -ne 0' after the param block. UPDATE: Today I am getting error for extra params! Wtf? I can not get -c and -f switches to work together (-noexit doesn't help). '-InputFormat none' for non-interactive comands to prevent powershell from waiting for input in some cases. Quotes in invocations: Except for double-quoted, parameters (without switch or after -C[ommand]) are taken precisely as script text except BEWARE OF special characters! Must use double-quotes to get metacharacters (other than space) to powershell. Determine powershell version: PowerShell [-C[ommand]] $psversiontable.psversion cmdlets are build-in commands (as opposed to standalone external executables). Are functions considered cmdlets?) *.ps1 scripts # Comments just like Bourne # including partial-line <#...multi-line #> I think ; delimiter only needed within {...} $variables @{hash initializer assignments} exit 1 # Works as you would expect and want. Interactively Ctrl-D/Z don't work. Built-in vars: https://www.tutorialspoint.com/powershell/powershell_special_variables.htm $MyInvocation.MyCommand (has .CommandType, .Name (basename), .Source or Definition (path) $ARGS $HOME (~ $ENV:USERPROFILE) $PID $PWD.path $LASTEXITCODE for Windows-based program execution, not cmdlets $MATCHES $PROFILE # Path of env-setup file $PSVERSIONTABLE $TRUE # Use this not True! $FALSE # Use this not False! $_ === $PSItem input pipeline record $MyInvocation. Awesome members. See elsewhere in this file. $? boolean. PROBLEMATIC! Don't know what it indicates because it's true when LASTEXITCODE is 0 or positive. $HOST Not useful. This is host APPLICATION. Seems missing vars hostname and current user. WORKAROUNDS: hostname: $hn = hostname; "...$(hostname)..."; $ENV:computername user name: whoami; $ENV:username If have privs from AMI profile can use https://docs.aws.amazon.com/powershell/latest/reference/ Generally don't need quotes, but do need if context doesn't make clear that it's a string. Seem to need them in variable assignment and in list elements. ("one", 'two') Multi-line quoting does work, with both single- and double-quotes (doesn't work from CMD). ${DRIVE:path} Drive letter is required but path may be relative. = ${X:file\path} === Get-Content file\path -Raw ${X:file\path} = === | Set-Content file\path GB, etc. abbreviation literals understood function fName ($param1, $param2) { body } I think body gets its own ARGS array Invoked as either: fName p1 p2 fName -param1 p1 -param2 p2 Returned value is the value of the expression, but somehow if you "Write-Output 'X'" inside the function then X makes it into the output [DOT_NET_NAMESPACE]::StaticMethod(params) e.g. casting: [DateTime]::Parse($aString) () === {} Dot operator "." is similar to UNIX shell's source/dot operator. Both 'try... catch' and 'trap' supported Enumerate like: 2..8 Forward-slashes always work for commands being invoked (whether from $var, ./bare/word, 'quoted/word', Start-Process) % is an alias for ForEach-Object path\to\exernalProgram.x arg1 "arg2", arg3 or succinctly: &path\to\p arg1 arg2 & $externalProgVar "arg1" arg, "arg3" & "path/to/extProg.x" "arg1" arg, "arg3" If 1st token (command) is either a $var or quoted, the must use & (or maybe .) # There is also '.'. Sometimes do need path, like ".\localScript.ps1". # Don't need to use a path. Honors PATH. Don't need quotes. Don't need & # unless command specification quoted. # Unlike CMD shell, PATH is strictly honored, and empty PATH segments have no # effect. To execute things in current directory without specifying the path # explicitly, add . to the $ENV:PATH! # Sets $LASTEXITCODE (return val integer) and $? boolean. # $? false if fails to execute the thing at all # Does not log anything or throw based on exit value or $? # For unknown reason, on UNIX invoked scripts must have an interpreter line # (i.e. can't make use of default interpreter). I believe that by default thrown errors just log and don't interrupt execution. For command invocations as documented in this section, you never get this from a command returning any particular value, only if the command can't be invoked. Start-Process ... Runs command in background, not associated with terminal. Start-Process command arg1, arg2 BUT if you use other switches then you need to pack args like so: @('arg1', 'arg2') useful switches: -WorkingDirectory x -NoNewWindow -PassThru -RedirectStandardError x -RedirectStandardInput y -RedirectStandardOutput z Returns output only if -PassThru set GITCH: There is no good way to close off stdinput with nul or /dev/null, etc. For ability to check or kill, give -PassThru switch and the x.ID is the PID. Can then Stop-Process -Id 123 Can check with : Get-Process -Id 123 If (bool expr) { some; things; } [ElseIf (be) { other; things; } ELSE { other; } {} blocks: Only need ; as command delimiter. Don't need ; at end. Env setup. File $PROFILE is like autoexec.bat but for PowerShell. Function 'Prompt' defines PowerShell prompt. Operators -eq, -ne, -lt, like (RE), -ceq, -[c]like *wild, -[c]contains N.b. -eq/like/contains do case-insensitive string comparisons. Must add the c prefix (like "-cne") for case-sensitive. $obj | Get-Member like typeof + object dump. Only reports on 1st $_ $var.getType() is just like typeof in normal languages. Can't run it against a literal value. Reading JSON: $var = Get-Content file.json | ConvertFrom-Json; Writing JSON: $outThing | ConvertTo-Json -Compress I/O N.b. Write-Error is a completely different thing from the other Write-Xs. (Much more complicated!) Output field delimiter is \r\n. 'Write-output one two' writes: "one\r\ntwo\r\n" Each parameter token written with a record delimiter. Write-Output (aka 'echo', 'write' aliases) and Write- + N.b. abbreviation "write-out" DOES NOT WORK! Debug, Verbose, Progress*, Information, Warning* (*=on by default) The stupid control variables are $XPreference and are set to: Continue (display) SilentlyContinue (no behavior) Stop (display and halt) Example: $VerbosePreference = 'Continue' Write-Verbose 'msg' 'msg' | Write-Verbose $fileContent = Get-Content file.txt -Raw # Always use -Raw if you want a single string. $reader = New-Object System.IO.StreamReader("in.csv") $writer = New-Object System.IO.StreamWriter("out.csv") # Copy first line over, with an extra ",DATE" $writer.WriteLine($reader.ReadLine() + ",DATE") # terminates with \r\n $writer.flush() # Process lines until in.csv ends while (($line = $reader.ReadLine()) -ne $null) { # Get index of last occurrence of "DATE: " $index = $line.LastIndexOf("DATE: ") # Replace last occurrence of "DATE: " with a comma $line = $line.Remove($index, 6).Insert($index, ',') # Write the modified line to the new file $writer.WriteLine($line) } # Close the file handles $reader.Close() $writer.Close() [cmdlet] Pipelines Last cmdlet in pipeline is implicitly Out-Default, which just formats to a stream and dumps to screen. Lines can end with | just like Bourne External executables get and write a text stream instead of objects. Input records converted to text then all that text piped into program stdin The output is just one text object per output line. Useful pipe cmdlets: Where-Object, Sort-Object, Measure-Object, Select-Object, ForEach-Object (%) all of these take a {...} param Where-Object IDIOTICALLY filters out where condition is true. I Guess $_ is the received pipeline record, so $_.Field is a field of input records. Last expression is the output pipeline record. $jc1, $jc2 | ForEach-Object { write-output $_.k1 } OR $jc1, $jc2 | ForEach-Object { $_.k1 } command | Out-String -Stream | Select-String -CaseSensitive -Pattern re # grep Format-Table outputs no objects, it just displays to screen But this is default text output formatting for objects anyway, so no reason to put it as last cmdlet. CSV ConvertFrom-Csv $csvText # Weakness here that it loads each line into a record Import-CSV file\path.csv Assumes comma-delimiter and header line. To specify cols explicitly and delim: Import-CSV file\path.csv -header ID,Name,Country -delimiter ';' Object instantiation: New-Object PSObject -prop @{Key1 = 'val1'; Key2 = 2;} Exceptions Generally set this so don't have to use switch for each cmdlet: $ErrorActionPreference=’Stop’ If not then only exit calls or final statement executed will determine exit val. Example: This will exit with success: dir bad; dir good Try { ... } Catch [System.Runtime.InteropServices.COMException] { Write-Warning "[CATCH] Communications Exception occurred!" BREAK } Catch { # Gets exception description $_ Write-Warning "[CATCH] Errors found during attempt:`n$_" BREAK } Finally { ... } Regular Expressions, Regexp, re. And other string matching. Match expression anywhere within candidate strings, so must use ^...$ to match entire. ALL matching is case-sensitive by default, I.e. '-ix' === '-x'. Boolean: x -cmatch 'RE' (also alternatives like -imatch and -cnotmatch) Populates $MATCHES precisely like JavaScript. But MATCH elements (other than 0) can also be indexed by specified name to take them out of the integer indexing. Use (?RE) rather than (RE). Select-String -CaseSensitive -Path file\path.txt -Pattern re # like file grep command | Out-String -Stream | Select-String -CaseSensitive -Pattern re # grep [bool] $boolVar = 1; or 0 or $true or $false; There is: 'string' -replace 'regexp', 'replStr' # N.b. this is replaceAll, not single replace In addition to the -*match* family, theres also the -*like* family for GLOB matching. N.b. the 'contains' and 'in' operators are for set inclusion, not string inclusion. GOTCHA: For string-contains/includes/substring check, use $string.contains NOT $array -contains Invoke-WebRequest $Body = @{ User = 'jdoe' password = 'P@S$w0rd!' } $LoginResponse = Invoke-WebRequest 'http://www.contoso.com/login/' -SessionVariable 'Session' -Body $Body -Method 'POST' $Session $ProfileResponse = Invoke-WebRequest 'http://www.contoso.com/profile/' -WebSession $Session $ProfileResponse Registry There are other methods. The paths are case-insensitive. https://docs.microsoft.com/en-us/powershell/scripting/getting-started/cookbooks/working-with-registry-entries?view=powershell-6 (Get-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\DateTime\Servers -Name 2).2 The Filter, Include, Exclude parameters are for keys (paths) not properties such as name. Env vars $z = $env:HOME Also works: $v = Get-Variable -name HOME -ValueOnly $env:X = 'eks why' Does not work: Set-Variable... List all: gci env:* | sort-object name List of hashes: $( ${k1 = v1; k2 = v2; }, ${...}, ) Array or List: @(1,2,"Hello World") You get: $list.length BUT SOMETIMES $list.Count ??? GOTCHA!: THERE IS NO RELIABLE AND DIRECT WAY TO push/pop/shift/unshift/delete(int) Loop over a list: Foreach ($el n $list) {... GOTCHA! The system automatically converts 1-length lists returned by function implementations to just the 1st element scalar. format-list -Property * is our friend. Double quotes work well. A few examples: "Get-WebConfiguration -filter system.webserver/directorybrowse | format-list -Property *" "Get-WebConfiguration -filter system.web/machinekey | format-list -Property *" Grab the ps object, pipe it, then format it based on property =D Registry entries: For list of allowed type values, see description for "-Type" switch in any reg command ref page like https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-itemproperty?view=powershell-7.1 Most importantly: String for REG_SZ Binary for REG_BINARY DWord (number) for REG_DWORD Single-quote path value if it has a string IMPORTANT! Do set -Type when using Set-ItemProperty with tpes other than REG_SZ (String), because automatically change the type based on what it thinks the new value should be. Dumb! WMI objects get-WmiObject. "scriptomatic" is a tool to generate PowerShell scripts for this. Include another script: At least for $PWD must start with ".\". . ".\file\path" Writing registry keys and values. Start paths with abbreviation like HKLM, HKCU, HKCC. New-Item -Path HKLM:\Software\Microsoft\Rpc -Name Internet [-Force] New-ItemProperty -Path HKLM:\Software\Microsoft\Rpc\Internet -Name Ports -PropertyType MultiString -Value @("5555", "5559") New-ItemProperty -Path HKLM:\Software\Microsoft\Rpc\Internet -Name UseInternetPorts -PropertyType String -Value Y Windows Features (incl. AD tools) Desired features: get-windowsfeature | Out-String -Stream | Select-String expr Installed features: get-windowsfeatures | where installed Install features: install-windowsfeature -name A B C [-whatif] foreach($v in (el1 el2 el3) { write-output "Next el($v)" } A couple commands can't be run simply from PowerShell because a power-shell command name conflicts. You get error message from the built-in that's accidentally invoked. To work around, use the filename extension like sc: use sc.exe something else that I can't remember Services natively Get-Service "*name wildcard" [-Exclude "ExecpName" Get-Service -DisplayName "*dn wildcard" Seems that just like sc.exec command, can't see the Log On user shown by services.msc properties. File existence/presence test: Test-Path [-PathType Leaf|Container] -LiteralPath $path OR [System.IO.File]::Exists($path) [System.IO.Directory]::Exists($path) These do work with relative paths, incl. unqualified. Variable existence/presence tests: Test-Path variable:x Logical Expressions !(p1) p1 -and p2 p1 -or p2 Zipping/Unzipping Requires PS 5.0 or later. Zip: Compress-Archive -LiteralPath inPath1,inPath2 -DestinationPath file[.zip] (output file suffix defaults to .zip). This flattens!!!! No param to not flatten! Unzip/Extraction: Expand-Archive -LiteralPath file.zip -DestinationPath outdir 'outdir' may use forward slashes, and all missing dirs are auto-created. -Force prevents confirmation question -PassThru lists contents NOT WORKING By defaults createst subdir of $PWD with base name of specified zip file (capitalization exactly as you specified). -Destination-Path dir # to override with specified new dir NO WAY to limit what gets extracted File checksums: Get-FileHash file/path [-Algorithm SHA1|SHA256|SHA384|SHA512|MD5] Algo defaults to SHA256. pause/sleep: Start-Sleep -s 2 OR in milliseconds: Start-Sleep -m 5 Current directory: Get-Location cmdlet. Must assign it to something. alias: $PWD. Much more convenient. N.b. IT IS NOT $ENV:PWD !! Task scheduler: schtasks Copy file: Copy-Item -LiteralPath 'input.file' -Destination output.file Delete file: Remove-Item -LiteralPath 'input.file" [-Recurse] (docs say recurse may not get all) String formatting / printf-like: 'one {0} two {1} three' -f 'alpha', 'beta' Entire text file contents to stdout: Get-Content file.path -Raw Entire text file contents to a variable: $v = Get-Content file.path -Raw Redirection to files like with > writes extra garbage. NOTHING LIKE CMD REDIRECTION! GOTCHA: Use System.IO.StreamWriter! E.g.: powershell "'one' > tfl.txt" writes 2 binaries and then the characters with null characters interspersed. Totally screwed up. Therefore, use: something | Set-Content -LiteralPath file.txt Set-Content -Value 'content' -LiteralPath file.txt and Add-Content for appending. Catching Exceptions. Caught exception is available as $_. To catch anything specifically you need to know the FQ exception name. You can determine that with catch { write-output $_.Exception.getType().FullName Then catch that explicitly with square-brackets: } catch [System.blah.blah.XException] { DOES NOT WORK WELL Concatenate lists: $concat = $l1 + $l2 Append item: $l += 'new el' Hash Tables are maps/hashes. $hash = @{} Logging. If streams not consumed then all end up in stdout other than Write-Error processed output goes to stderr. Write-Verbose Writes to verbose steam only if $VerbosePreference changed Write-Information Writes to info steam only if $InformationPreference changed Write-Output Writes to the default pipeline! Write-Warning Writes to console. Governed by $WarningPreference. Adds "WARNING" prefix, depending on version. Write-Debug Writes to console. Writes only if $DebugPreference changed Write-Host Writes to console or info stream, depending on version. Confusingly affected by InformationAction Write-Error Writes a structured error thing instead of what you ask it to. By default haults execution. Quiet verbose commands like 'New-Item -ItemType Directory...' commands: loud-command | Out-Null cd: Set-Location -LiteralPath D:/path N.b. -Path switch is implicit; generates no output unless you give -PassThru switch; unlike CMD it changes drive too. Generally, -Path vs. -LiteralPath -Path honors wildcards, default value of home dir, push/pop. -LiteralPath does work with relative paths too, incl. unqualified. New-Item is an exception in that it supports -Path but not -LiteralPath. Why??? There is no -Literal... alternative corresponding to -File. Script directory is $PSScriptRoot from PS v. 3.0 which is OOTB Win 8 / Server 23012. Script name (if any) is $MyInvocation.MyCommand.Name shutdown: run shutdown command same as from CMD rename: Rename-Computer -NewName 'whatever' [-Restart] syntax check a script: powershell "Get-Command -syntax ./scriptname.ps1" Much too verbose when run from pwsh. absolutize path: (resolve-path -LiteralPath srcPath).path N.b. the node must exist or this will throw. tests: The [bool] type returns the data as either true or false instead of returning the actual data your searching for if ([bool]((Get-Content -Path "C:\listOfNames.txt" -Raw) -like '*BrazilRP*')) { move files and directories; move-item The correct way to make a function call with parameters is to delimit the params with SPACES!: fName $p1 $p2 $p3 TAIL: get-content -tail 50 -wait filenamel.txt KILL Process: if(! $Cred){ $Cred = Get-Credential } $servers=@( 'host1' 'host2' ) $servers | ForEach-Object { Invoke-Command -ComputerName $_ -Credential $Cred { write-host "===========================================" [System.Net.Dns]::GetHostByName(($env:computerName)).Hostname Get-WmiObject Win32_NTDomain -Authentication Default Test-ComputerSecureChannel -Verbose Stop-Process -Name wrapper-windows-x86-64 -Force Stop-Process -Name java -Force sleep -s 3 nltest /sc_verify:fanniemae.com } } Get service binpath (easier than wmic or sc.exe): (get-wmiobject -query 'select * from win32_service where name="winrm"').pathname Simple string list usage: # Create and populate a string list $stringList = [System.Collections.Generic.List[string]]::new() $stringList.Add("Item1") $stringList.Add("Item2") $stringList.Add("Item3") # Display element count $elementCount = $stringList.Count Write-Output "The list contains $elementCount items"