DCSIMG
May 2010 - Posts - Shay Levy

Shay Levy

If you repeat it, PowerShell it!

News


btn_donate_LG

View Shay Levy's profile on LinkedIn Follow Shay Levy at Twitter Shay Levy's Facebook profile Subscribe to my FriendFeed


site statistics




May 2010 - Posts

How to Discover Unused Mailboxes

In Exchange 2007, and later, we can use the Get-MailboxFolderStatistics cmdlet to retrieve information about the folders in a specified mailbox, including the number and size of items in the folder, the folder name and ID, and other information.
If we add the IncludeOldestAndNewestItems switch parameter, Get-MailboxFolderStatistics will also return the dates of the oldest and newest items in each folder. Using that information we can determine the last time a message was sent from a mailbox.

Using the script below, we can get all user mailboxes that did not sent an email in the last X days. To make the script run faster, we will process just the SentItems folder (using the FolderScope parameter). Note that items in folders are stamped with a GMT time so we use the ToLocalTime() method to convert the value to local time.

 

$xDays = 60

Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | Foreach-Object
   
   $si= Get-MailboxFolderStatistics $_ -IncludeOldestAndNewestItems -FolderScope SentItems
 
   if($si.NewestItemReceivedDate -AND (New-TimeSpan $si.NewestItemReceivedDate.ToLocalTime()).Days -ge $xDays)
   {
      $_
   }

}

QuickTip: How to validate a UNC path

The System.Uri class provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI. One of its properties is IsUnc . IsUnc gets whether the specified Uri is a universal naming convention (UNC) path and returns a Boolean value that is True if the Uri is a UNC path or False otherwise.

 

#Requires -Version 2.0

function Test-UNC
{
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias('FullName')]
        [System.String[]]$Path
    )

    process
    {
        foreach($p in $Path)
        {
            [bool]([System.Uri]$p).IsUnc   
        }
    }
}

Usage

# 1. Test the paths of a remote share
PS > Get-ChildItem \\server\share | Test-UNC
True
True
(...)

# 2. Pipe values
PS > "\\server\share","C:\autoexec.bat" | Test-UNC
True
False

# 3. Test command line values
PS > Test-UNC -Path "\\server\share","C:\autoexec.bat","file://server/filename.ext"
True
False
True

# 4. Test local paths
PS > Get-ChildItem C:\ | Test-UNC
False
False
(...)

PowerShell freenode IRC channel (WPF client)

The PowerShell IRC chat room (or the PowerShell ‘Virtual User Group’ as Jaykul likes to call it) on freenode, is one of the places where you can get answers to your PowerShell questions. There are plenty of PowerShell gurus in there to help you, and with Connect-PSVUG you can connect to the chat room without installing an IRC client.

The Connect-PSVUG (Connect PowerShell Virtual User Group) function is a WPF window that hosts a free open source (web based) IRC client. Just run Connect-PSVUG,  wait for the window to show, choose yourself a nickname (the channel name is already populated) and click the connect button.

 

After the function has been executed you get your prompt back so you can continue to work in your current session. If you want the window to stay on top of other windows, press ALT+T. Pressing that sequence again toggles the action. When you close the shell the IRC client is closed as well.

 

#requires -version 2

function Connect-PSVUG
{

$rs=[RunspaceFactory]::CreateRunspace()
$rs.ApartmentState = "STA"
$rs.ThreadOptions = "ReuseThread"
$rs.Open()
$ps = [PowerShell]::Create()
$ps.Runspace = $rs

$null = $ps.AddScript({

Add-Type -AssemblyName PresentationFramework

$window = New-Object Windows.Window
$window.Title = 'PowerShell Freenode IRC channel'
$window.WindowStartupLocation = 'CenterScreen'

$wb = New-Object Windows.Controls.WebBrowser
$wb.Source='http://webchat.freenode.net/?channels=powershell'

$grid = New-Object Windows.Controls.Grid
$grid.Children.Add($wb)
$window.Content = $grid

$window.Add_Loaded({
$window.Activate()
})

$window.Add_KeyUp({
if($_.Key -eq 'T' -AND [Windows.Input.ModifierKeys]::Alt)
{
$window.Topmost=!$window.Topmost
}
})

$null = $window.ShowDialog()

}).BeginInvoke()
}

How to get the default parameter values of a cmdlet

PowerShell’s help is the first place we check for detailed descriptions of cmdlet parameters. We can find if a parameter is required or not, if it’s a named parameter or positional, if it has a default value, and so on. Let’s take a look at the help for Get-WMIObject’s NameSpace parameter:

 

PS > Get-Help Get-WMIObject -Parameter NameSpace

-Namespace <string>
When used with the Class parameter, this parameter specifies the WMI repository
namespace where the referenced WMI class is located. When used with the List
parameter, it specifies the namespace from which to gather WMI class information.

Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false

Plenty of information, but one value is missing, the parameter ‘Default value’. As you can see, it’s empty. This is not the case for all parameters of all cmdlets. Here are two examples, the first doesn’t have much to offer while the second has more information:

 

PS > Get-Help Get-WMIObject -Parameter * | Format-Table Name,DefaultValue –AutoSize

Name DefaultValue
---- ------------
Amended
AsJob
Authentication
Authority
Class
ComputerName
Credential
DirectRead
EnableAllPrivileges
Filter
Impersonation
List
Locale
Namespace
Property
Query
Recurse
ThrottleLimit



PS > Get-Help Test-Connection -Parameter * | Format-Table Name,DefaultValue -AutoSize

Name DefaultValue
---- ------------
AsJob False
Authentication 4
BufferSize 32
ComputerName
Count 4
Credential Current user
Delay 1 (second)
Impersonation 3
Quiet False
Source Local computer
ThrottleLimit 32
TimeToLive 80

 

There are several reasons for missing default values. The first and the obvious, someone has to manually write the value (if there is one) in the cmdlet maml file. Second, the value has to be specified by the caller or to be assigned at runtime. Third, not all default values are “constant” values. Default values can be complicated expressions such as script code, Regular Expression patterns, Throw statements, and so writing them in the help files can be confusing or even make no sense. To get an idea, take a look at the Validate* parameter attributes in the about_Functions_Advanced_Parameters help topic.

To fill the gap (where possible) and get additional default values that are not specified in help we can get the name of the .NET Framework type that implements any given cmdlet and instantiate the type to reveal the values. To get the cmdlet implementing type we use the Get-Command cmdlet:

 

PS > Get-Command Get-WMIObject | Format-List i*

ImplementingType : Microsoft.PowerShell.Commands.GetWmiObjectCommand

 

Now that we have the ImplementingType we can use it as the argument for the TypeName parameter of the New-Object cmdlet.

PS > New-Object –TypeName Microsoft.PowerShell.Commands.GetWMIObjectCommand


Class :
Recurse : False
Property : {*}
Filter :
Amended : False
DirectRead : False
List : False
Query :
AsJob : False
Impersonation : Impersonate
Authentication : Packet
Locale :
EnableAllPrivileges : False
Authority :
Credential :
ThrottleLimit : 32
ComputerName : {localhost}
Namespace : root\cimv2
ParameterSetName :
MyInvocation : System.Management.Automation.InvocationInfo
InvokeCommand : System.Management.Automation.CommandInvocationIntrinsics
Host :
SessionState :
Events :
JobRepository :
InvokeProvider :
Stopping : False
CommandRuntime :
CurrentPSTransaction :
CommandOrigin : Internal

 

Note that the list above doesn't include dynamic parameters. Now that we know the default value for NameSpace (and ComputerName) we can write shorter commands. All Win32_* WMI classes reside in the root\cimv2 namespace so we don’t have to specify it. For example:

Get-WMIObject –Class Win32_Process –ComputerName localhost –NameSpace root\cimv2

Can be shortened to:

Get-WMIObject –Class Win32_Process


Time to update my Get-Parameter function :)

 

 

QuickTip: Additional PowerShell Registry drives

Windows PowerShell provides access to the system registry via two PowerShell drives: HKLM and HKCU, which maps to the HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER registry hives respectively.

PS > Get-PSDrive -PSProvider Registry

Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE

 

To add registry drives for other hives like HKEY_USERS,HKEY_CLASSES_ROOT etc, we can use the New-PSDrive cmdlet with the provider path of each hive (the $null assignment is used to suppress the output of each New-PSDrive command).

$null = New-PSDrive -Name HKU   -PSProvider Registry -Root Registry::HKEY_USERS
$null = New-PSDrive -Name HKCR -PSProvider Registry -Root Registry::HKEY_CLASSES_ROOT
$null = New-PSDrive -Name HKCC -PSProvider Registry -Root Registry::HKEY_CURRENT_CONFIG

 

PS > Get-PSDrive -PSProvider Registry

Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
HKCC Registry HKEY_CURRENT_CONFIG
HKCR Registry HKEY_CLASSES_ROOT
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
HKU Registry HKEY_USERS

 

You won't be able to access the new registry drives after you exit a current PowerShell session. To make them persistent add the commands to your PowerShell profile.

 

Going back in time

Did it ever happen to you to spend a decent amount of time working on a piece of code in the console, making it work and then closing the shell thinking to yourself: Oh cool, I found a solution!
5 minutes later you try to recreate what you did and something is not working? You bang your head against the wall wishing that time travel was possible so you could go back in time and take just a sneak peak at what you wrote.

Time travel is not possible. At the moment. But even without it, you can ‘recover’ the code. The Start-Transcript cmdlet is here to help you.

With Start-Transcript you can record whole or a part of a Windows PowerShell session in a text file. The transcript includes all command(s) you have typed and all output that appears in the console. Running Start-Transcript without any parameters starts a transcript in the default file location (MyDocuments folder) with a file name in the format of "PowerShell_transcript.{0:yyyyMMddHHmmss}.txt".

 

PS > Start-Transcript
Transcript started, output file is C:\Users\UserName\Documents\PowerShell_transcript.20100510162158.txt
 

We can change the transcript file location with the Path parameter, prevent any existing files from being overwritten
with the NoClobber parameter and even add a transcript to the end of an existing file by specifying the Append switch parameter. For more code examples type: ‘Get-Help Start-Transcript –Examples’ in your console.

It is possible to call Start-Transcript without giving it a file path and give the file name a format of your choice.
When Start-Transcript is executed it looks for a global variable named 'TRANSCRIPT'. If the variable exists, its value is used as the path to the transcript file.

 

function Save-Transcript{
$global:TRANSCRIPT = "D:\Scripts\Transcripts\PSLOG_{0:dd-MM-yyyy}.txt" -f (Get-Date)
Start-Transcript -Append
}

Save-Transcript
 

With the function above (which I use in my profile), you get a new transcript file per day. If the file exists, the transcript is appended, otherwise a new file is created. There is nothing to worry about anymore, you can safely close your console session at any time knowing that all your work is saved.

 

See Also:
Stop-Transcript

Teaching PowerShell

JBTLogo

I’m pleased to announce that I’ve joined the ranks of John Bryce Training – The Leading IT Education center in Israel and by the end of the month I am going to start teaching PowerShell. My first (3-day) PowerShell 2.0 class (private class for a large local company, sorry you can’t join) is already booked. I’m so excited :)

Get-Parameter - Learn more about your cmdlets

Back in 2007 I wrote a function as a learning tool to discover cmdlet parameters and their attributes. Since then the function has evolved and has much more information to offer. In my day-to-day scripting this is the function I use the most. It gives me a quick view of all I need to know about any cmdlet parameters, and much much more.

Here’s how the result of Get-Parameter looks when you query the Select-Object cmdlet (gprm is an alias for Get-Parameter).

PS > gprm Select-Object
ParameterSet: DefaultParameter


Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
*ExcludeProperty String[] Named False False {exc} False False
*ExpandProperty String Named False False {exp} False False
*First Int32 Named False False {f} False False
InputObject PSObject Named True False {i} False False
*Last Int32 Named False False {l} False False
*Property Object[] 1 False False {p} False False
*Skip Int32 Named False False {s} False False
Unique SwitchParameter Named False False {u} False False




ParameterSet: IndexParameter


Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
*Index Int32[] Named False False {ind} False False
InputObject PSObject Named True False {inp} False False
Unique SwitchParameter Named False False {u} False False

 

What you can do with Get-Parameter?

First let’s take a look at the function syntax:

Get-Parameter [-Command] <String> [[-Name] <String[]>] [-SortBy <String>] [-CommandType <String>] [-IncludeCommonParameters] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]

 

Short Parameters description:

  • Command: The cmdlet name or its alias. If the value is an alias Get-Parameter will resolve the cmdlet name.
  • Name: Optional. A list of parameter names to get, wildcards are permitted.
  • SortBy: Optional. The name of the column to sort on. Notice that the columns use short names so the output can fit in your shell. Values for this parameter must use the short names. For example:
    - Pos is a short name for Position.
    - BV  is a short name for ValueFromPipeline. It determine if the parameter accepts pipeline input by property name.
    - BP is a short name for ValueFromPipelineByPropertyName. It determine if the parameter accepts pipeline input by value .
  • Descending: Optional. Sorts the objects in descending order. The default is ascending order.
  • CommandType: Optional. Specifies the command type (cmdlet,function, etc) when there are more than one commands with the same name.
  • IncludeCommonParameters: Controls whether to display Common Parameters in the output.

One feature I wanted to add to the function is the ability to include/exclude output of common parameters. In previous versions of Get-Parameter, I created a collection of strings that contained all (v2) common parameter names and tested against it inside the function. That was OK but I was looking for a way to get the parameter names programmatically instead of hard coding them. Finally I've found a way with the CommonParameters Class.

PS > [System.Management.Automation.Internal.CommonParameters].GetProperties() | `
Select-Object -ExpandProperty Name







Verbose
Debug
ErrorAction
WarningAction
ErrorVariable
WarningVariable
OutVariable
OutBuffer

Another column I want to add is the DefaultValue (DV) for each parameter, working on it…

 

What can you learn from the output?

Before we continue let’s talk about Parameter Sets, In a nutshell:

‘Windows PowerShell uses the concept of parameter sets to enable you to write a single cmdlet that can perform different actions for different scenarios. Parameter sets allow you to expose different parameters to the user and to return different information based on the parameters specified by the user.’

More information about Parameter Sets and how to use them can be found HERE and HERE. Now let’s take a look at Get-Random:

PS > gprm Get-Random


ParameterSet: RandomNumberParameterSet

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
*Maximum Object 1 False False {ma} False False
*Minimum Object Named False False {mi} False False
SetSeed Nullable`1 Named False False {s} False False


ParameterSet: RandomListItemParameterSet

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
*Count Int32 Named False False {c} False False
*InputObject Object[] 1 True False {i} True False
SetSeed Nullable`1 Named False False {s} False False

We can see that Get-Random has two parameter sets: RandomNumberParameterSet and RandomListItemParameterSet, each with its own set of parameters. To quickly identify unique parameters in each set, each unique parameter name starts with ‘*’. All other parameters belongs to all parameter sets.

One important thing to keep in mind about parameter sets is that they are mutually exclusive. What does that mean? It means that you cannot write a command that combines ‘unique’ parameters from multiple parameter sets. With the above Get-Random output, you cannot write a command that use’s both the ‘Maximum’ and ‘Count’ parameters. You’ll get an error:

Get-ChildItem : Parameter set cannot be resolved using the specified named parameters.

Below is an explanation of each column Get-Parameter produces. You can get more information about parameter attribute declaration HERE.

 

Type

This property tells us the parameter .NET type. If a parameter has ‘[]’ at the end of its name, we know that it accepts a collection of values of that type. For example, the Include parameter can take a collection of strings so we can specify them at the command line (delimited by a comma):

PS > Get-ChildItem –Path d:\temp\* –Include *.ps1,*.txt

 

Position (Pos)

The Position column tells us if the parameter is a named parameter or positioned. If the position is a number we can write its value without the parameter name otherwise (Named) the parameter must be referenced by its name . The Path (Position 1) and Filter (Position 2) parameters are both positioned so we can write the command like so:

PS > Get-ChildItem d:\temp *.ps1


Which is the same as:

PS > Get-ChildItem –Path d:\temp –Filter *.ps1

 

BV and BP

The next two columns are BV (ValueFromPipeline) and BP (ValueFromPipelineByPropertyName). These attributes (Boolean) controls whether the parameter accepts pipeline input.

ValueFromPipeline – If the value is set to $true the parameter binds the value from the incoming pipeline object (not just a property of the object).

ValueFromPipelineByPropertyName - If the value is set to $true the parameter binds the value from the incoming pipeline object that has the same name or the same alias as this parameter. For example, if the cmdlet/function has a Name parameter and the pipeline object also has a Name property, the value of the Name property is assigned to the Name parameter.

One neat technique you can use with these attributes is when you need to bind values from incoming pipeline objects that do not necessarily have corresponding property names nor aliases. Here’s an example, you want to ping a list of server names and you want to pipe the list to the Test-Connection cmdlet: 

PS > "Server1","Server2","Server3" | Test-Connection
Test-Connection : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

 

So, attempting to pipe the names directly to Test-Connection fails, let’s take a look at the ComputerName parameter:

PS > gprm Test-Connection -Name com*


ParameterSet: Default

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
ComputerName String[] 1 False True {CN, IPAddress, __SERVER, Dest…} True False 

 

The ‘ComputerName’ parameter is required (Mandatory), it’s positioned as the first parameter and it accepts pipeline input ByPropertyName. The values we’re piping doesn’t have a property named ‘ComputerName’. The typical way to handle these kind of situations is to use the Foreach-Object cmdlet:

PS > "Server1","Server2","Server3" | Foreach-Object { Test-Connection -ComputerName $_} 

 

And here comes the trick. We can get the same result without the intermediate cmdlet. If the ‘ComputerName’ parameter accepts pipeline input, either by property name or by value, then we can take advantage of that and ‘force’ the cmdlet to bind the ‘ComputerName’ value for the incoming object by using ScriptBlock Parameters.

PS > "Server1","Server2","Server3" | Test-Connection -ComputerName {$_}

Source Destination IPV4Address IPV6Address Bytes Time(ms)
------ ----------- ----------- ----------- ----- --------
PC1 Server1 192.168.1.1 {} 32 0
(...)

 

Pretty cool, ah?

Aliases

The Aliases property contains parameter built-in aliases. Parameters can also have aliases. You can use the aliases instead of the parameter names when you type or specify the parameter in a command. For example, most cmdlets that support the –ComputerName parameter defines ‘Cn’ as an alias for this parameter:

PS > gprm gwmi -name comp*



ParameterSet: query


Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- -- ------- --------- -------
*ComputerName String[] Named False False {Cn, co} False False
(...)

In addition to the built-in parameter aliases Get-Parameter also adds another value (co). This value is the least amount of characters you need to type to disambiguate the parameter from other parameter names. If you see more than one value in the Aliases column then the last ones was added by Get-Parameter and all the previous are the built-in. More information on Parameter Aliases can be found in this page.

 

Mandatory

The Mandatory property indicates if the parameter is required. If a required parameter is not provided when the cmdlet is invoked, PowerShell prompts the user for a parameter value. Sometimes, all you need to know is the list of mandatory parameters for a given command without PowerShell prompting you for each one. If so, sort the output by this property and look for all $true values. The following example shows the necessary parameters you should use to create new mailbox on Exchange 2007 (Name,UserPrincipalName,Database and Password):

PS > gprm New-Mailbox -SortBy Mandatory -Descending



ParameterSet: User


Name Type Pos Aliases Mandatory Dynamic
---- ---- --- ------- --------- -------

*Password SecureString Named {p} True False
Database DatabaseIdParameter Named {da} True False
UserPrincipalName String Named {u} True False
Name String 1 {n} True False
(...)

 

Dynamic

Cmdlets, Providers and Advanced Functions can define parameters that are available to the user under special conditions, such as when the argument of another parameter is a specific value. These parameters are added at runtime and are referred to as dynamic parameters because they are added only when they are needed.

With this column you can identify the Dynamic parameters of the command in question. Keep in mind that the result is based on the shell’s provider location. See the ‘Dynamic Parameters’ section in the about_Functions_Advanced_Parameters help topic or read the updated online version or the ‘FINDING DYNAMIC PARAMETERS’ section in the about_providers file.

That’s all. There’s a lot to digest but as you can see there’s a lot to learn from the output. With Get-Parameter you can see the ‘Big Picture’. As always, there’s plenty of room for improvements and I’m open for suggestions.

Get-Parameter is available at PoshCode.org.