Skip to main content

Invoking Code Repeatedly

Sometimes you might want to run some command multiple times until it runs successfully. Here is a function that shows a way to do this:


#requires -Version 2
function Invoke-CodeRepeatedly
{
  param
  (
    [Parameter(Mandatory=$true)]
    $ScriptBlock,

    $RepeatCount = 4,
    $SleepMilliseconds = 3000

  )
  
  $c = 0
  do
  {  
    try
    {
      $ErrorActionPreference = 'Stop'
      & $ScriptBlock
      $doagain = $false
    }
    catch
    {
      $c++
      $doagain = $c -lt $RepeatCount 
      if ($doagain -eq $false) { break }
      Write-Verbose "Error $_ trying again..."
      Start-Sleep -Milliseconds $SleepMilliseconds
    }
  } while ($doagain)
}

You submit a script block with your code to Invoke-CodeRepeatedly and use -RepeatCount to specify how often the code should be tried. It repeats 4 times by default. -SleepMilliseconds is used to define the pause between tries. It waits for 3 seconds by default.

You could test this function like this:


Invoke-CodeRepeatedly -ScriptBlock { New-Item $env:temptestfolder -ItemType Directory  } -Verbose

When you run it the first time, it succeeds and creates a new folder. When you run it later, it fails because the folder is already present. It will now try again three times (a total of 4 tries) and waits 3 seconds between each try. If you delete the folder from another PowerShell while the function runs, you will see the code succeed again.

Twitter This Tip! ReTweet this Tip!


Posted Feb 26 2016, 06:00 AM by ps1

Saving Persistent Data

Sometimes a script needs to save information in a persistent way. Maybe you have a list of computers that you'd want to contact, but only some are online. Then your script should report which computers could be contacted, so when you run the script later again, these computers can be skipped.

CSV files are a perfect way for this. Let's create a simple CSV file like this:


$data = @'
ComputerName, Status, Date
microsoft.com,,
dell1,,
powershell.com,,
powershellmagazine.com,,
'@

$Path = "$env:tempnetwork.csv"
$data | Set-Content -Path $Path

Next, a script should try and ping the computers in this list, but only if they did not respond before. Here is how a script can update the CSV accordingly:


#requires -Version 2
(Import-CSV -Path $Path) |
ForEach-Object {
    $pc = $_.ComputerName

    if ($_.Status -ne $true)
    {
        Write-Warning "Checking $PC..."
        $_.Status = Test-Connection -Count 1 -ComputerName $pc -ErrorAction SilentlyContinue -Quiet
        $_.Date = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    }
    else
    {
        Write-Warning "Skipping $PC..."
    }
    $_
} | Export-CSV -Path $Path -Encoding UTF8 -NoTypeInformation

When you run the script, it tries to contact all computers in the CSV list. If it can contact a computer, this is reported back to the CSV list. So if you run the script a second time, it will only try and contact the remaining computers that did not answer before.

Twitter This Tip! ReTweet this Tip!


Posted Feb 25 2016, 06:00 AM by ps1

Use WMI the Modern Way!

WMI is a powerful technique to find out information about local or remote computers, and you may have used Get-WmiObject before to do so (if not, you may want to google for this cmdlet).

In PowerShell 3.0, a new cmdlet named Get-CimInstance was added that seems to do exactly what Get-WmiObject does. But that is not true. While Get-WmiObject always uses DCOM to access remote systems, Get-CimInstance can use both DCOM and the new PowerShell remoting techniques. Even more important, with Get-CimInstance you can use persistent remoting sessions and reuse them across many calls. This can speed up remote access considerably.

Here is a sample that retrieves processor information using the old and the new way. Just make sure you replace the name of the remote system with a system that is online, and make sure remoting permissions are set up appropriately to access the remote system:


#requires -Version 3

# classic approach, always uses DCOM
Get-WmiObject -Class Win32_Processor -ComputerName SERVER1

# modern approach, you choose the protocol
# and you can reuse sessions
$option = New-CimSessionOption -Protocol Dcom
$session = New-CimSession -SessionOption $option -ComputerName SERVER1 

Get-CimInstance -ClassName Win32_Processor -CimSession $session 

Remove-CimSession -CimSession $session

Twitter This Tip! ReTweet this Tip!


Posted Feb 24 2016, 06:00 AM by ps1

Using .NET Types Directly

Cmdlets contain pure .NET code, so thanks to cmdlets, you do not need to directly touch .NET code. You can, however. Here are a number of sample calls that illustrate how .NET methods can be accessed:


#requires -Version 2
[System.Convert]::ToString(687687687, 2)

[Math]::Round(4.6)

[Guid]::NewGuid()

[System.IO.Path]::ChangeExtension('c:test.txt', 'bak')

[System.Net.DNS]::GetHostByName('dell1')
[System.Net.DNS]::GetHostByAddress('192.168.1.124')

[Environment]::SetEnvironmentVariable()

# dangerous, save your work first
[Environment]::FailFast('Oops')

Add-Type -AssemblyName PresentationFramework
$dialog = New-Object Microsoft.Win32.OpenFileDialog
$dialog.ShowDialog()
$dialog.FileName

Twitter This Tip! ReTweet this Tip!


Posted Feb 23 2016, 06:00 AM by ps1

Using Workflows to Parallelize Code

If you want to execute more than one thing at once, there are many ways to implement this in PowerShell. One may be the use of workflows. They were introduced in PowerShell 3.0:


#requires -Version 3
workflow Test-ParallelForeach
{
  param
  (
    [String[]]
    $ComputerName
  )

  foreach -parallel -throttlelimit 8 ($Machine in $ComputerName)
  {
    "Begin $Machine"
    Start-Sleep -Seconds (Get-Random -min 3 -max 5)
    "End $Machine"
  }
}

$list = 1..20

Test-ParallelForeach -ComputerName $list | Out-GridView

Test-ParallelForeach runs a list of computers (in this sample, it is a list of numbers). They all run at the same time. To control resource usage, the parallel loop does use a throttle limit of 8, so the 20 computers in this sample run in groups of 8.

Note that workflows do require more knowledge about their architecture and limitations. This example focuses on the parallel loop technique supported by workflows.

Twitter This Tip! ReTweet this Tip!


Posted Feb 22 2016, 06:00 AM by ps1

Decorate Scripts with #requires Statements

PowerShell supports a number of #requires statements. Technically they are comments but PowerShell checks the requirements, and if they are not met, it won't run a script. In addition, #requires statements tell you quickly what a script needs to run.


#requires -Modules PrintManagement
#requires -Version 3
#Requires -RunAsAdministrator

#requires statements need to be the first statements in a script, and they work only for saved scripts.

Twitter This Tip! ReTweet this Tip!


Posted Feb 19 2016, 06:00 AM by ps1

Do Not Mix Different Objects!

If you do output completely different objects, you may lose information. Take a look at this example:


#requires -Version 2

$hash = @{
Name = 'PowerShell Conference EU'
Date = 'April 20, 2016'
City = 'Hannover'
URL = 'www.psconf.eu'
}
New-Object -TypeName PSObject -Property $hash 

$b = Get-Process -Id $pid
$b

When you run the code, you get this:


  
Date           URL           Name                     City    
----           ---           ----                     ----    
April 20, 2016 www.psconf.eu PowerShell Conference EU Hannover
                             powershell_ise
 

It seems almost all properties from $b (process) were lost. The reason: PowerShell outputs objects in real time, and the first emitted object determines which properties are shown in the table. Any subsequent object will be fitted into this table.

If you must output different objects, pipe them to Out-Host. Each time you pipe to Out-Host, PowerShell creates a new output with new table headers.

Twitter This Tip! ReTweet this Tip!


Posted Feb 18 2016, 06:00 AM by ps1

Getting an Excuse

Here is a quick way of getting a good excuse - provided you have Internet access:


#requires -Version 3
function Get-Excuse
{
  $url = 'http://pages.cs.wisc.edu/~ballard/bofh/bofhserver.pl'
  $ProgressPreference = 'SilentlyContinue'
  $page = Invoke-WebRequest -Uri $url -UseBasicParsing
  $pattern = '(?m)<br><font size = "+2">(.+)'
  if ($page.Content -match $pattern)
  {
    $matches[1]
  }
}

It illustrates how you can use Invoke-WebRequest to download the HTML content of a webpage, then use regular expressions to scrape content from that page.

Twitter This Tip! ReTweet this Tip!


Posted Feb 17 2016, 06:00 AM by ps1

Who is Listening? (Part 2)

If you run at least Windows 8 or Windows Server 2012, you can use Get-NetTcpConnection to find out which network ports are in use, and who is listening on these ports.

The script below not only lists the ports in use but also the processes that do the listening. If the process is "svchost", the script finds out the names of the services that are run by this process:


#requires -Version 3 -Modules NetTCPIP

$service = @{}
$Process = @{
  Name = 'Name'
  Expression = {
    $id = $_.OwningProcess
    $name = (Get-Process -Id $id).Name 
    if ($name -eq 'svchost')
    {
      if ($service.ContainsKey($id) -eq $false)
      {
        $service.$id = Get-WmiObject -Class win32_Service -Filter "ProcessID=$id" | Select-Object -ExpandProperty Name
      }
      $service.$id -join ','
    }
    else
    {
      $name
    }
  }
}

Get-NetTCPConnection | 
Select-Object -Property LocalPort, OwningProcess, $Process | 
Sort-Object -Property LocalPort, Name -Unique

The result may look similar to this:


  
LocalPort OwningProcess Name                                                   
--------- ------------- ----                                                   
      135           916 RpcEptMapper,RpcSs                                     
      139             4 System                                                 
      445             4 System                                                 
     5354          2480 mDNSResponder                                          
     5985             4 System                                                 
     7680           544 Appinfo,BITS,Browser,CertPropSvc,DoSvc,iphlpsvc,Lanm...
     7779             4 System                                                 
    15292          7364 Adobe Desktop Service                                  
    27015          2456 AppleMobileDeviceService                               
(...)
 

Twitter This Tip! ReTweet this Tip!


Posted Feb 16 2016, 06:00 AM by ps1

Who Is Listening? (Part 1)

The good oldfashioned netstat.exe can tell you the ports that applications listen on. The result is plain-text, though. PowerShell can use regular expressions though to split the text into CSV data, and ConvertFrom-Csv can then turn the text into real objects.

This is just an example of how PowerShell can use even the most basic data:


#requires -Version 2
NETSTAT.EXE -anop tcp| 
Select-Object -Skip  4|
ForEach-Object -Process {
  [regex]::replace($_.trim(),'s+',' ')
}|
ConvertFrom-Csv -d ' ' -Header 'proto', 'src', 'dst', 'state', 'pid'|
Select-Object -Property src, state, @{
  name = 'process'
  expression = {
    (Get-Process -PipelineVariable $_.pid).name
  }
} |
Format-List

The result may look similar to this:


  
src     : 0.0.0.0:135
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service, 
          AdobeIPCBroker...}

src     : 0.0.0.0:445
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service, 
          AdobeIPCBroker...}

src     : 0.0.0.0:5985
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service, 
          AdobeIPCBroker...}

src     : 0.0.0.0:7680
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service, 
          AdobeIPCBroker...}

src     : 0.0.0.0:7779
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service, 
          AdobeIPCBroker...}
 

Twitter This Tip! ReTweet this Tip!


Posted Feb 15 2016, 06:00 AM by ps1