Friday, April 29, 2016

Keeping Your Modules Up-To-Date

Once you start downloading modules from the PowerShell Gallery (or via PowerShellGet in general), you get version control automatically. You can always review the modules that were installed, and compare their versions with the versions available in the repository.

This way, it's easy to find out the modules that need an update. The following code assumes that you know the PowerShell Gallery already. If not, visit powershellgallery.com.

Get-InstalledModule | ForEach-Object {
    try
    {
        $module = Find-Module -Name $_.Name -ErrorAction Stop
        $newVersion = $module.Version
        $needsUpdate = $_.Version -lt $newVersion
    }
    catch
    {
        $newVersion = 'no longer available'
        $needsUpdate = $true
    }
    $_ | Add-Member -MemberType NoteProperty -Name VersionAvailable $newVersion 
    $_ | Add-Member -MemberType NoteProperty -Name NeedsUpdate $needsUpdate 
    $_
  } |
  Select-Object -Property Name, NeedsUpdate, Version, VersionAvailable |
  Out-GridView

When you run this code, you get a list of modules that were installed via PowerShellGet, and version information that tells whether a module needs an update. The output looks similar to this:

 
Name                   NeedsUpdate Version  VersionAvailable   
----                   ----------- -------  ----------------   
Pester                       False 3.4.0    3.4.0              
xDefender                    False 0.2.0.0  0.2.0.0            
xRobocopy                     True 1.1.0.0  1.2.0.0            
xWebAdministration            True 1.8.0.0  1.9.0.0            
CommunityAnalyzerRules        True 0.0.0.1  no longer available
FabrikamAnalyzerRules         True 0.0.0.1  no longer available
ISESteroids                   True 2.3.0.46 2.3.0.64           
nScriptAnalyzerRules         False 0.1      0.1                
NtpTime                      False 1.1      1.1                
Pscx                         False 3.2.1.0  3.2.1.0            
PSScriptAnalyzer              True 1.1.0    1.4.0              
ScriptAnalyzer                True 0.5.1.0  no longer available
xDSCResourceDesigner          True 1.6.0.0  1.7.0.0 
 

Twitter This Tip! ReTweet this Tip!

Thursday, April 28, 2016

Better Prompting for Mandatory Parameters

When you do not submit an argument to a mandatory parameter, PowerShell prompts you for a value. You don't have much control over how this is done, and most find it ugly.

To use your own prompting, make the parameter optional, and provide code that is executed to define the default value. When no argument is submitted, PowerShell tries to retrieve the default value, and executes your own prompting logic.

Take a look at what this looks like. With the upcoming example, you get a new function called Get-Birthday that is fully automatable, so you can submit arguments. If you don't, you get a pretty nice prompt:

 
PS> Get-Birthday  -Date 1/1/2000
You are born on a  Saturday!
 
PS> Get-Birthday 
Enter your birthday [>>>] no!
Should be a date. Try  again! 
Enter your birthday [>>>] ok
Should be a date. Try  again! 
Enter your birthday [>>>] 1/1/2000
You are born on a Saturday!
 

We are going to re-use the Get-UserInput function from the previous tip to create the actual custom prompt.:

function Get-UserInput
{
  param
  (
    [Parameter(Mandatory=$true)]
    $Prompt,
    [ScriptBlock]
    $Validator = $null,
    $ErrorMessage = 'Incorrect. Try again!'
  )

  do
  {
    Write-Host "$Prompt " -NoNewline
    Write-Host "[>>>] " -ForegroundColor Yellow -NoNewline
    $userinput = Read-Host

    if ($Validator -ne $null)
    {
      $_ = $userinput
      $ok = & $Validator 
    }
    else
    {
      $ok = $true
    }

    if (!$ok)
    {
      Write-Host $ErrorMessage -ForegroundColor Red
    }
  } until ($ok)

  $userinput
}

function Get-Birthday
{
  param
  (
    [DateTime]
    $Date = (Get-UserInput -Prompt 'Enter your birthday' -Validator { $_ -as [DateTime] } -ErrorMessage 'Should be a date. Try again!')
  )

  $realDate = Get-Date -Date $Date
  $weekday = $realDate.DayOfWeek
  "You are born on a $weekday!"
}

Twitter This Tip! ReTweet this Tip!

Wednesday, April 27, 2016

Use Mandatory Parameters

Mandatory parameters are cool: you can submit values to them for automated solutions, and you can omit them and get prompted interactively.

Here is an example:

function Get-Birthday
{
  param
  (
    [DateTime]
    [Parameter(Mandatory=$true)]
    $Date
  )

  $realDate = Get-Date -Date $Date
  $weekday = $realDate.DayOfWeek
  "You are born on a $weekday!"
}

When you run Get-Birthday without arguments, PowerShell prompts you. Since the parameter expected a DateTime type, you even get re-prompted if your input does not match that type:

 
PS> Get-Birthday
cmdlet Get-Birthday at command pipeline position 1
Supply values for the following parameters:
Date: oh
Cannot recognize "oh" as a System.DateTime due to a format  error.
Date: ups
Cannot recognize "ups" as a System.DateTime due to a format  error.
Date: 1/1/2000
You are born on a Saturday! 
 

Twitter This Tip! ReTweet this Tip!

Tuesday, April 26, 2016

Accept User Input with Validator

Of course you can use Read-Host to ask a user for some input, or use mandatory parameters. They all are kind of ugly, and have next to no validation. Here is a function that makes things easier:

function Get-UserInput
{

  param
  (
    [Parameter(Mandatory=$true)]
    $Prompt,

    [ScriptBlock]
    $Validator = $null,

    $ErrorMessage = 'Incorrect. Try again!'

  )
  do
  {
    Write-Host "$Prompt " -NoNewline
    Write-Host "[>>>] " -ForegroundColor Yellow -NoNewline
    $userinput = Read-Host

    if ($Validator -ne $null)
    {
      $_ = $userinput
      $ok = & $Validator 
    }
    else
    {
      $ok = $true
    }

    if (!$ok)
    {
      Write-Host $ErrorMessage -ForegroundColor Red
    }
  } until ($ok)

  $userinput
}

Get-UserInput just needs a prompt text, an error text, and a validator. The validator can be any piece of PowerShell code. It checks the user input. Here is a simple example, asking for a birthday:

Get-UserInput -Prompt 'Enter your birthday' -Validator { $_ -as [DateTime] } -ErrorMessage 'Should be a date. Try again!'

And this is the result:

 
Enter your birthday [>>>] won't tell you!
Should be a date. Try  again! 
Enter your birthday [>>>] 1/1/2001
1/1/2001
 

Twitter This Tip! ReTweet this Tip!

Monday, April 25, 2016

Keeping Track of Installed Modules

When you start using PowerShellGet to download modules from the PowerShell Gallery (http://ift.tt/1uN5nBB) or your own NuGet repositories, you may want to keep track of the modules you installed. Here is a one-liner that shows you what was installed, and where:

 
PS> Get-InstalledModule | Select-Object Name, InstalledLocation 

Name                   InstalledLocation                                        
----                   -----------------                                        
Pester                 C:\Program Files\WindowsPowerShell\Modules\Pester\3.4.0  
xDefender              C:\Program Files\WindowsPowerShell\Modules\xDefender\0...
xRobocopy              C:\Program Files\WindowsPowerShell\Modules\xRobocopy\1...
xWebAdministration     C:\Program Files\WindowsPowerShell\Modules\xWebAdminis...
CommunityAnalyzerRules C:\Users\Tobias\Documents\WindowsPowerShell\Modules\Co...
FabrikamAnalyzerRules  C:\Users\Tobias\Documents\WindowsPowerShell\Modules\Fa...
ISESteroids            C:\Users\Tobias\Documents\WindowsPowerShell\Modules\IS...
nScriptAnalyzerRules   C:\Users\Tobias\Documents\WindowsPowerShell\Modules\nS...
NtpTime                C:\Users\Tobias\Documents\WindowsPowerShell\Modules\Nt...
Pscx                   C:\Users\Tobias\Documents\WindowsPowerShell\Modules\Ps...
PSScriptAnalyzer       C:\Users\Tobias\Documents\WindowsPowerShell\Modules\PS...
ScriptAnalyzer         C:\Users\Tobias\Documents\WindowsPowerShell\Modules\Sc...
xDSCResourceDesigner   C:\Users\Tobias\Documents\WindowsPowerShell\Modules\xD...
 

Twitter This Tip! ReTweet this Tip!

Friday, April 22, 2016

Tailing Log Files

You can ask PowerShell to monitor a text-based log file and output any changes. It is a one-liner:

Get-Content c:\Windows\WindowsUpdate.log -Wait -Tail 0

Typically, Get-Content would only read the text content from the given file. -Wait instructs PowerShell to keep waiting for more information, so PowerShell stops and monitors the file for subsequent changes.

-Tail tells the cmdlet how many of the existing content you want to read. If you just want to monitor for new content, set it to 0.

Note that there are situations when the file cache will prevent real time notifications. So if you change the file, and PowerShell won't report these changes immediately, then this may be due to some caching. One workaround is to "trigger" the cache in intervals by calling Get-Item with the path to the monitored file. This of course would have to be done in a separate thread.

Twitter This Tip! ReTweet this Tip!

Thursday, April 21, 2016

Checking Paths and Files for Illegal Characters

In the previous tip we illustrated how you can get a list of characters that are illegal in paths or file names. However, what would be the best and easiest way to check whether any of these characters actually existed in a given path or file name?

Simply use the -cnotmatch operator. Here are two functions that test whether paths and file names contain illegal characters. They also show how to encode ASCII codes of illegal characters in a RegEx-compliant way:

function Test-PathName ($Path)
{
    $invalidPath = -join [System.IO.Path]::GetInvalidPathChars().Foreach{('\x{0:x2}' -f ([Byte][Char]$_))}
    $Path -cnotmatch "[$invalidPath]"
}

function Test-FileName($Path)
{
    $invalidName = -join [System.IO.Path]::GetInvalidFileNameChars().Foreach{('\x{0:x2}' -f ([Byte][Char]$_))}
    $Path -cnotmatch "[$invalidName]"
}

Test-PathName -Path "c:\this\is\ok"
Test-PathName -Path "c:\this|is\notok"
Test-FileName -Path "ok.txt"
Test-FileName -Path "not:ok.txt"

Twitter This Tip! ReTweet this Tip!

Wednesday, April 20, 2016

Finding Invalid File and Path Characters

When dealing with files and folders, some characters are considered illegal. Here is an easy way to get a list of the characters that Windows declared as illegal:

$invalidPath = -join [System.IO.Path]::GetInvalidPathChars()
$invalidPath

$invalidFile = -join [System.IO.Path]::GetInvalidFileNameChars()
$invalidFile

In a path, a pipeline symbol is considered illegal, and that is exactly what you get when you try and use a pipeline symbol inside a path:

$path = "c:\test\invalid|file\path.txt"
Test-Path $path
 
Test-Path : Illegal characters in path.
At line:2 char:1
+ Test-Path $path
+ ~~~~~~~~~~~~~~~

Twitter This Tip! ReTweet this Tip!

Tuesday, April 19, 2016

Operate Your Own PowerShell Gallery

With PowerShellGet, you can easily share PowerShell code. Go to http://ift.tt/1uN5nBB and download the MSI installer to install PowerShellGet if you are not running the latest PowerShell 5.0 version.

In the previous tip, we showed how to search for and install modules from the PowerShell Gallery. To create your own local gallery, and share resources internally, simply create an SMB file share-based repository in your own environment. It's a one-liner!

Just make sure you set up a file share first where you have read and write permissions. Next, run this (we used \\server1\Gallery for our file share, so make sure you adjust this part):

 
PS> Register-PSRepository -Name MyTeam -SourceLocation \\server1\Gallery -InstallationPolicy Trusted

PS> Get-PSRepository

Name      PackageManagementProvider InstallationPolicy   SourceLocation                        
----      ------------------------- ------------------   --------------                        
PSGallery NuGet                     Untrusted            https://www.powershell...
MyTeam    NuGet                     Trusted              \\server1\Gallery
 

To upload content to your own gallery, use Publish-Module or Publish-Script, and specify your own internal repository.

An SMB file share-based repository is an excellent way for teams and departments to work together.

Twitter This Tip! ReTweet this Tip!

Monday, April 18, 2016

Identifying Special Characters

If you must ensure that a string contains only a given set of characters, try this:

$text = 'tobias.weltner'
$hasOtherCharacters = $text -cmatch '[^a-zA-Z]'

"$text has illegal characters? $hasOtherCharacters"

It is really simple: In the brackets, add the characters that are legal. Whenever PowerShell now finds a character in the string that is not in this list, you receive a $true, else $false.

This example checks whether the input was a number or a dot:

$text = '12.98'
$hasOtherCharacters = $text -cmatch '[^0-9.]'

"$text has illegal characters? $hasOtherCharacters"

The result would be:

 
12.98 has illegal characters? False
 

If the text contained a comma instead of a dot, the result would be different because the comma is considered illegal:

$text = '12,98'
$hasOtherCharacters = $text -cmatch '[^0-9.]'

"$text has illegal characters? $hasOtherCharacters"
 
12,98 has illegal characters? True
 

Twitter This Tip! ReTweet this Tip!