powershell_notes - jonathanmorgan/shell_scripts GitHub Wiki

Basics

tutorials

development

running powershell scripts

Using Invoke-Command

In the background

On remote computer, in PSSession

Operators

- operators doc: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.1
- assignment operators: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_assignment_operators?view=powershell-7.1
- https://ss64.com/ps/syntax-operators.html

back-tick escape character

Naming conventions

Parameters

Script Parameters

  • basics: https://jmmv.dev/2020/10/powershell-cmdlet-params.html

  • parameters must be first in script.

  • I can't get defaults to work inside param structure... so set defaults later.

  • types of parameters: https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/types-of-cmdlet-parameters?view=powershell-7.1

  • Example:

      #==============================================================================#
      # parameters - must be at top of script (no code between top and "Param").
      #==============================================================================#
    
      #[CmdletBinding()]
      Param(
          [Parameter(HelpMessage="Turn on debug messages.")]
          [switch]$DebugFlag,
          [Parameter(HelpMessage="Clean up after a test run.")]
          [switch] $CleanupFlag,
          [Parameter(HelpMessage="The path where you want output file stored.")]
          [string] $OutputFile
      )
    
      # store parameters in expected variables
      $start_date_time_stamp = Get-Date -UFormat "%Y-%m-%d_%H-%M-%S"
      $debug_flag = $DebugFlag
      $cleanup_flag = $CleanupFlag
      if ( $OutputFile )
      {
          $output_file_path = $OutputFile
      }
      else
      {
          $output_file_path = ".\${start_date_time_stamp}-logging_test.tsv"
      }
    
      Write-Host "params: debug_flag = ${debug_flag}; cleanup_flag = ${cleanup_flag}; output_file_path = ${output_file_path}"
    

Function Parameters

  • Notes:

  • Example:

      function choose_config_value()
      {
          [CmdletBinding()]
          Param(
              [Parameter( HelpMessage = "Value for this config property passed on command line." )]
              $CommandLineValue,
              [Parameter( HelpMessage = "Value for this config property from config file." )]
              $ConfigValue,
              [Parameter( HelpMessage = "Default value for this config property." )]
              $DefaultValue,
              [Parameter( HelpMessage = "(optional) Convert to a particular type?" )]
              [string] $ConvertToType,
              [Parameter( HelpMessage = "Turn on debug messages." )]
              [switch] $OutputDebug
          )
    
          # return reference
          $value_out = $null
    
          # declare variables
          $my_debug_flag = get_shared_debug
          #$my_debug_flag = $true
          $value_type = $null
          $value_type_name = $null
    
          if ( $OutputDebug.IsPresent -eq $true )
          {
              $my_debug_flag = $true
          }
    
          # got a command line value?
          if ( ( $null -ne $CommandLineValue ) -and ( $CommandLineValue -ne "" ) )
          {
              # got command line value - this overrides config file.
              $value_out = $CommandLineValue
          }
          elseif ( ( $null -ne $ConfigValue ) -and ( $ConfigValue -ne "" ) )
          {
              # got config file value - use it.
              $value_out = $ConfigValue
          }
          else
          {
              # nothing set on command line or in config file - use default.
              $value_out = $DefaultValue
          }
    
          # are we converting?
          if ( $ConvertToType -eq $Script:CONVERT_TO_BOOLEAN )
          {
              # check if string
              $value_type = $value_out.GetType()
              $value_type_name = $value_type.Name
              if ( $value_type_name -eq $Script:TYPE_NAME_STRING )
              {
    
                  # string - convert to boolean!
                  $value_out = [System.Convert]::ToBoolean( $value_out )
    
              }
          }
          elseif ( $ConvertToType -eq $Script:CONVERT_TO_INT )
          {
              # cast to integer
              $value_out = [int]$value_out
          }
    
          # return it
          if ( $my_debug_flag -eq $true )
          {
              Write-Host "Value: `"${value_out}`""
              Write-Host "----> chosen from:"
              Write-Host "- CLI: `"${CommandLineValue}`""
              Write-Host "- config: `"${ConfigValue}`""
              Write-Host "- default: `"${DefaultValue}`""
              Write-Host "- ( Convert to: ${ConvertToType} )"
          }
    
          return $value_out
    
      } #-- END function choose_config_value() --#
    

Variables

Data Types

Numbers

Dates

Date Math

Time Spans

Strings

special characters

Find and replace

delimited strings - split

command output

path strings - Split_Path

Split-Path - Parse strings that are paths to get elements of path (file name, containing folder path, etc.).

convert boolean string to Boolean

Arrays

Hash Tables

Directories and Files

Get-Content - look into files

Examples:

Get-Item - retrieve item(s) at a path

Get-ChildItem - cleaner way to get items within folder.

Get-Location - get current working directiory

Set-Location - change working directory

Allows you to change current working directory in side a script, similar to CD command.

Test-Path - check if file or directory exists

New-Item - create file/folder/other item

New-Item - Create new item (either file, folder, or other types like registry entry).

Copy-Item

Copy file - Copy-Item

Move-Item

Move file - Move-Item

Remove-Item

Remove-Item lets you delete files and folders (and other items).

Invoke-Item - open item with default program

Invocation

Running commands stored in variables

& operator

  • & $command_name $param_list

    • & command accepts the string name of the command you want to run, and then a list of the params you want to pass to the command. It returns the output from execution of the command.

    • this is more secure (less threat of command injection).

    • Example:

        $test_command = "mkdir"
        $test_args = @( "test3" )
        $test_output = & $test_command $test_args
        Write-Host "command output (test3): ${test_output}"
      

Invoke-Expression

& vs. Invoke-Expression

Capturing output of a command

Processes

Start-Process

Program Control

if-then-else

for loop

  • from: https://adamtheautomator.com/powershell-for-loop/

    • Many examples here.
  • syntax:

      for (<Initial iterator value>; <Condition>; <Code to repeat>)
      {
          <Statement list>
      }
    
  • example with integer index:

      # loop to create $output_file_count files in output folder.
      for ( $output_file_number = 1; $output_file_number -le $output_file_count; $output_file_number++ )
      {
          # write random file to output folder
          $output_file_name = "${current_timestamp}-${output_file_number}"
          $output_file_path = write_random_file "${output_file_name}" "${output_file_size_bytes}" "${rd_output_folder_path}"
          Write-Host "--------> command output (file name = ${output_file_name}; file size = ${output_file_size_bytes}; output folder: ${rd_output_folder_path}):`n${output_file_path}"
      }
    

for-each

while

Start-Sleep

Start-Sleep lets you pause execution of a powershell script for any number of minues and milliseconds, summed if both are present.

Program/Script structure

Functions

function return

Classes

modules - function libraries

Modules are files of powershell functions. They can also contain module variables, to help with processing.

Notes:

Import-Module

Entity Information

User information

System Information

System Performance Profiling

This is a mess on windows - nothing equivalent to top to tie it all together and put a bow on it for you.

Notes:

CPU and processes

The different methods to capture CPU usage seem to return different values at roughly the same point in time, and to not correlate with each other over time. So... perhaps just buy monitoring software?

Example:

$cpu_wmic_loadpercentage = wmic cpu get loadpercentage
$cpu_w32p_loadpercentage = (Get-CimInstance -Class Win32_Processor).LoadPercentage
$cpu_w32p_load_average = (Get-WmiObject -Class win32_processor -ErrorAction Stop | Measure-Object -Property LoadPercentage -Average | Select-Object Average).Average
if ( $include_cpu_average_in -eq $true )
{
    $cpu_average = ( GET-COUNTER -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 2 -MaxSamples 5 | Select-Object -ExpandProperty countersamples | Select-Object -ExpandProperty cookedvalue | Measure-Object -Average ).average
}
else
{
    $cpu_average = "N/A"
}
$proc_count = (Get-Process).Count
$service_count = (Get-Service).Count
$running_service_count = (Get-Service | Where-Object Status -eq "Running").Count

Notes:

memory

Example:

# get memory info. Uses `WIN32_OperatingSystem` class:
$comp_object = Get-WmiObject -Class WIN32_OperatingSystem
$mem_total = $comp_object.TotalVisibleMemorySize
$mem_free = $comp_object.FreePhysicalMemory
$mem_used = $mem_total - $mem_free
$swap_total = $comp_object.TotalVirtualMemorySize
$swap_free = $comp_object.FreeVirtualMemory
$swap_used = $swap_total - $swap_free

Notes:

  • This code is based on a more thorough function: https://docs.microsoft.com/en-us/archive/blogs/timid/getting-computer-memory-usage

  • Full function included below in case the archive ever goes away:

      function Get-ComputerMemory {
           param (
               [Parameter( 
                   Position = 0,  
                   ValueFromPipeline = $true, 
                   ValueFromPipelineByPropertyName = $true 
               )] [String[]]$ComputerName = @($env:COMPUTERNAME.ToLower()),
               [int]$digits = 3
           );
           process {
               foreach ($computer in $ComputerName ) {
                   $object = 1 | Select-Object ComputerName, TotalPhysicalMemoryGB, FreePhysicalMemoryGB, UsedPhysicalMemory, TotalVirtualMemoryGB, FreeVirtualMemoryGB, UsedVirtualMemory;
          
                   Write-Progress "Get-WmiObject Win32_ComputerSystem" "-ComputerName $computer"
                   $wmiCompSys = Get-WmiObject Win32_ComputerSystem -ComputerName $computer;
                   if (!$wmiCompSys) { 
                       Write-Warning "Unable to get Win32_ComputerSystem data from $computer"; 
                   }
    
                   Write-Progress "Get-WmiObject Win32_OperatingSystem" "-ComputerName $computer"
                   $wmiOpSys = Get-WmiObject Win32_OperatingSystem -ComputerName $computer;
                   if (!$wmiOpSys) {
                       Write-Warning "Unable to get Win32_OperatingSystem data from $computer";
                   }
          
                   $object.ComputerName = $computer.ToLower();
          
                   # normalize to GB. NOTE: Win32_OperatingSystem returns KB while Win32_ComputerSystem returns bytes.
                   $totalPhys = $wmiCompSys.TotalPhysicalMemory / 1GB;
                   $freePhys = $wmiOpSys.FreePhysicalMemory / 1MB;
                   $totalVirt = $wmiOpSys.TotalVirtualMemorySize / 1MB;
                   $freeVirt = $wmiOpSys.FreeVirtualMemory / 1MB
          
                   $object.TotalPhysicalMemoryGB = ("{0:N$digits}" -f ($totalPhys + .5)) -as [float];
                   $object.FreePhysicalMemoryGB = ("{0:N$digits}" -f ($freePhys + .5)) -as [float];
                   $object.UsedPhysicalMemory = [int]((100 * (1 - ($freePhys/$totalPhys))) + .5);
    
                   $object.TotalVirtualMemoryGB = ("{0:N$digits}" -f ($totalVirt + .5)) -as [float];
                   $object.FreeVirtualMemoryGB = ("{0:N$digits}" -f  ($freeVirt + .5)) -as [float];
                   $object.UsedVirtualMemory = [int]((100 * (1 - ($freeVirt/$totalVirt))) + .5);
          
                   $object;
               }
           }
      }
    
  • Basic information:

Output

to screen - Write-Host

to file

Redirect output

when invoking script or calling command

within script - Start-Transcript

Tasks

Create random data

Verifying file hashes

  • Checking hash values for files in Powershell:

CertUtil

  • CertUtil -hashfile <file_path> <hash_algorithm>

  • WHERE:

    • <file_path> = path to file you want to hash.
    • <hash_algorithm> = hash algorithm (“sha256”, “md5”, etc.)

Get-FileHash

Zip files

One uses Compress-Archive and Expand-Archive to create and unpack zip files in Powershell.

Tutorials:

Compress-Archive

Expand-Archive

JSON

ConvertFrom-Json

Specific programs

R

Running an R program in a powershell script:

  • First, know where your R install is (example: C:\Program Files\R\R-4.1.1) You'll need to be able to find the \bin\x64\ folder inside to use Rscript.exe to run scripts in powershell.
  • potentially enable my unsigned powershell script to run: set-executionpolicy unrestricted
  • to run R script in shell: <path_to_bin_folder>\Rscript.exe <path_to_script>

SAS

Running a SAS program inside a powershell script:

xcopy (and robocopy and rsync)

xcopy

Documentation:

Arguments:

  • /s = subdirectories
  • /e = include empty folders
  • /k = preserve read-only
  • /r = copies read-only files
  • /h = copies hidden and system files.
  • /o = copies file ownership and DACL
  • /x = copies file audit and SACL, also implies /o
  • /y = suppresses prompting
  • /z = copies over a network in restartable mode
  • /b = copies symbolic link instead of files
  • /i = create destination if it does not exist.

Standard xcopy command most similar to rsync -avWH:

xcopy a: b: /s /e /h /i /y /b /z  # network "restartable" mode.
xcopy a: b: /s /e /h /i /y /b /x  # NOT network "restartable" mode, instead maintain window security information.

Notes:

  • for "standard command", had to remove syncing windows equivalent of permissions (/x) because it can't copy security while in restartable mode (/z). Error:

    • The /Z and /O (or /X) options conflict: cannot copy security in restartable mode.

robocopy (NT xcopy)

Robocopy is an updated version of xcopy with many additional features. I think it works the same as xcopy for existing command strings...?

Documentation:

Other notes:

rsync

  • a - archive (-rlptgoD, no -AXUNH)
  • v - verbose
  • W - whole-file copy - not just deltas.
  • H - preserve hard links...?
  • (R) - relative path names (not important)
  • r - recursive
  • l - links - include symlinks.
  • p - perms - copy permissions
  • t - times - preserve modification times
  • g - group - preserve group
  • o - owner - preserve owner (super-user-only)
  • D - --devices, --specials -- devices - preserve device files -- specials - preserve special files

rsync -avWH

troubleshooting

unknown protocol - path starts with Microsoft.PowerShell.Core\FileSystem::\\

  • if you open a powershell into a network share folder by holding the left shift key and right-clicking to get context menu, then choose "Open PowerShell window here", if your script relies on file paths, this could break your script. If you see the current folder as something like:

      Microsoft.PowerShell.Core\FileSystem::\\<share_name>\<path>
    
    • This path won't work for some file path based command-lets, and you'll get message(s) about "unsupported protocol":

        WARNING: Error creating FileStream or StreamWriter object for file name 'Microsoft.PowerShell.Core\FileSystem::\\<share_name>\<path>': Exception calling ".ctor" with "2" argument(s): "The given path's format is not supported."  
      
    • To resolve this, open the share using it's drive letter, then navigate to that same folder, and all should work.

⚠️ **GitHub.com Fallback** ⚠️