Parsing Raw Text (Part 3)

In the previous tip we illustrated how you can use Select-String to find lines in raw text containing a specific word. It took some effort to extract the actual value(s) representing a given pattern:

 
PS C:> $data = ipconfig | select-string 'IPv4' 
PS C:> [regex]::Matches($data,"b(?:d{1,3}.){3}d{1,3}b") | Select-Object -ExpandProperty Value

192.168.2.112
 

This effort is not necessary, though, because Select-String is already using a regular expression match, and returns match objects.

 
PS C:> ipconfig | 
  Select-String 'b(?:d{1,3}.){3}d{1,3}b' | 
  Select-Object -Property *



IgnoreCase : True
LineNumber : 16
Line       :    IPv4 Address. . . . . . . . . . . : 192.168.2.112
Filename   : InputStream
Path       : InputStream
Pattern    : b(?:d{1,3}.){3}d{1,3}b
Context    : 
Matches    : {192.168.2.112}

IgnoreCase : True
LineNumber : 17
Line       :    Subnet Mask . . . . . . . . . . . : 255.255.255.0
Filename   : InputStream
Path       : InputStream
Pattern    : b(?:d{1,3}.){3}d{1,3}b
Context    : 
Matches    : {255.255.255.0}

IgnoreCase : True
LineNumber : 19
Line       :                                        192.168.2.1
Filename   : InputStream
Path       : InputStream
Pattern    : b(?:d{1,3}.){3}d{1,3}b
Context    : 
Matches    : {192.168.2.1}
 

So you can use a simple Where-Object with the -like operator to do pre-filtering, identifying only the lines you are after (i.e. the ones containing the word “IPv4”), then submit a RegEx pattern to Select-String, and evaluate the results:

 
PS C:> ipconfig | 
  # do raw prefiltering and get only lines containing this word
  Where-Object { $_ -like '*IPv4*' } |
  # do RegEx filtering using a pattern for IPv4 addresses
  Select-String 'b(?:d{1,3}.){3}d{1,3}b' | 
  # get the matching values
  Select-Object -ExpandProperty Matches |
  # get the value for each match
  Select-Object -ExpandProperty Value

192.168.2.112
 

Twitter This Tip! ReTweet this Tip!

Parsing Raw Text (Part 2)

In the previous tip we explained how you can use Select-String and a regular expression to extract valuable information from raw text results:

 
PS C:> $data = ipconfig | select-string 'IPv4' 
PS C:> [regex]::Matches($data,"b(?:d{1,3}.){3}d{1,3}b") | Select-Object -ExpandProperty Value

192.168.2.112
 

PowerShell supports the -match parameter which also matches regular expressions. It can only find one match per line, though. In most scenarios, this is not a problem though because most often, there is only one match per line anyway. All you need to do is use -match inside a pipeline so the raw input is processed line by line:

 
PS C:> ipconfig | 
  # do raw filtering to only get lines with this word
  Where-Object { $_ -like '*IPv4*' } |
  # do RegEx filtering to identify the value matching the pattern
  Where-Object { $_ -match 'b(?:d{1,3}.){3}d{1,3}b' } | 
  # return the results from -match which show in $matches
  Foreach-Object { $matches[0] }

192.168.2.112
 

Twitter This Tip! ReTweet this Tip!

Parsing Raw Text (Part 1)

Sometimes, you may want to extract valuable information from pure text results. One easy way is the use of Select-String. This example extracts only the text lines containing “IPv4”:

 
PS C:> ipconfig | Select-String 'IPv4'

   IPv4 Address. . . . . . . . . . . : 192.168.2.112
 

If you are just interested in the actual IP address, you can refine the result and use a regular expression to extract the value you are after:

 
PS C:> $data = ipconfig | select-string 'IPv4' 
PS C:> [regex]::Matches($data,"b(?:d{1,3}.){3}d{1,3}b") | Select-Object -ExpandProperty Value

192.168.2.112
 

[Regex]::Matches() takes the raw data and a regular expression pattern describing what you are after. The content matching the pattern can then be found in the property “Value”.

Twitter This Tip! ReTweet this Tip!

Adjusting Simple UIs

In the previous tip you learned how you can use Show-Command to create simple UIs for text-based commands:

#requires -Version 3.0

function Send-MailMessageUI
{
  Show-Command -Name Send-MailMessage
}

Send-MailMessageUI

If you’d like to adjust the number of parameters that show up in the UI, simply write your own functions.

In the below example, Send-MailMessage is wrapped inside a custom function that exposes only some of the properties, and initializes others (like SMTP server and credentials) internally.

Here is a very simple email sender form that just shows the text boxes to send emails:

#requires -Version 3.0

function Send-MailMessageCustomized
{
  param
  (
    [Parameter(Mandatory)]
    [string]
    $From,
    
    [Parameter(Mandatory)]
    [string]
    $To,
    
    [Parameter(Mandatory)]
    [string]
    $Subject,
    
    [Parameter(Mandatory)]
    [string]
    $building,
    
    [switch]
    $BodyAsHTML
  )
  $username = 'mymailusername'
  $password = 'mymailpassword' # Dangerous, never hardcode! Consider using Get-Credential instead.
  $myServer = 'mail.mymailserver.mycompany.com'
  
  $passwordSecure = $password | ConvertTo-SecureString -AsPlainText -Force
  $myCred = New-Object -TypeName PSCredential($username, $passwordSecure)
  
  Send-MailMessage -From $From -To $To -Subject $Subject -building $building -BodyAsHtml:$BodyAsHTML -SmtpServer $myServer -Encoding UTF8 -Credential $myCred
}

function Send-MailMessageUI
{
  Show-Command -Name Send-MailMessageCustomized
}

Send-MailMessageUI

Twitter This Tip! ReTweet this Tip!

Creating Simple UIs

Function and cmdlet parameters basically are the technique how PowerShell creates „user interfaces“. These text-based interfaces can easily be turned into graphical interfaces.

If you’d like to send a mail message, you could use Send-MailMessage and provide the details via text-based parameters. Or, you could create a graphical interface and name it Send-MailMessageUI:

#requires -Version 3.0
 
function Send-MailMessageUI
{
  Show-Command -Name Send-MailMessage
}
 
Send-MailMessageUI

Now, when you run Send-MailMessageUI, all parameters are turned into text fields and checkboxes. Even a non-scripter can now fill out this form, then click „Run“ to execute the command.

Twitter This Tip! ReTweet this Tip!

Extending Robocopy

PowerShell can add value to existing commands such as robocopy. Take a look at the below function–it uses robocopy to copy files, and adds the ability to perform a „Flat Copy“ as well as the option to open the destination folder after the copy is done:

#requires -Version 3.0

function Copy-FileWithRobocopy
{
  param
  (
    [Parameter(Mandatory)]
    [string]$Source,
    
    [Parameter(Mandatory)]
    [string]$Destination,
    
    [string]$Filter = '*',
    
    [int]$RetryCount = 0,
    
    [string]$ExcludeDirectory = '',
    
    [switch]$Open,
    
    [switch]$FlatCopy,
    
    [switch]$NoRecurse 
  )
  
  $Recurse = '/S'
  if ($NoRecurse) { $Recurse = '' }
  
  robocopy.exe $Source $Destination $Filter /R:$RetryCount $Recurse /XD $ExcludeDirectory
  
  if ($FlatCopy)
  {
    Get-ChildItem -Path $Destination -Recurse -Filter $Filter | 
      Move-Item -Destination $Destination -Force
    Get-ChildItem -Path $Destination -Directory | 
      Remove-Item -Recurse -Force
  }
  
  if ($Open)
  {
    explorer $Destination
  }
}

This would copy all log files from any subfolder inside the Windows folder to a new folder named c:logs, and performs a flat copy:

 
PS>  Copy-FileWithRobocopy -Source $env:windir -Destination c:logs -Filter *.log -FlatCopy -Open
 

Before you use this on production systems, take a look at how –FlatCopy works: it simply looks for any files inside the destination folder that match the specified filter, and moves them to the root, then deletes all folders.

So duplicate files will be overwritten, and if the destination folder contained other data before in subfolders, these will be deleted. It’s a very simple approach that works for many use cases but leaves a lot of room for improvement.

Twitter This Tip! ReTweet this Tip!