To convert a date/time string from GMT (or any other time zone)
[DateTime]::Parse('2015-05-08T15:17:52Z')Source : #PSTip Converting to the local time
To convert a date/time string from GMT (or any other time zone)
[DateTime]::Parse('2015-05-08T15:17:52Z')Source : #PSTip Converting to the local time
How often do you use the Count property of an object to get the number of items in it ?
$SomeArray.count
The problem is that if you try to get the Count property on a null object or on a object that is not a collection or an array it will throw an exception.
The solution is to use the Measure-Object cmdlet :
($SomeArray | measure-object).count
Sometimes you want to call a powershell script from the command line and get an ERRORLEVEL a the exit if an exception is throw in the script (ex: With a scheduled task).
In Powershell v2 you could do this :
powershell.exe -noprofile -command ". c:\test-exit.ps1 -erroraction stop;exit $LastExitCode"
Unfortunately this does not work anymore in Powershell v4 (always return ERRORLEVEL 0). It looks like the $LastExitCode is not define anymore for a script by Powershell (only for an external process). But Powershell seems to now behave as expected and exit with an ERRORLEVEL=1 if any error occurs in your script. So now, the following code should works :
powershell.exe -noprofile -command ". c:\test-exit.ps1 -erroraction stop"
If you really want to check if your script was successful and return a special ERRORLEVEL, you should use the $? variable :
powershell.exe -noprofile -command ". c:\test-exit.ps1 -erroraction stop;if ($?){exit 0}else{exit 99}"
Don’t forget :
You can use the –ArgumentList parameter of the import-module cmdlet to pass arguments when loading a module.
You should use a param bloc in your module to define your parameters :
param(
[parameter(Position=0,Mandatory=$false)][boolean]$BeQuiet=$true,
[parameter(Position=1,Mandatory=$false)][string]$URL
)Then call the import-module cmdlet like this :
import-module .\myModule.psm1 -ArgumentList $True,'http://www.microsoft.com'
As may have already noticed, you can only supply values (no names) to –ArgumentList. So you should define you parameters carefully with the position argument.
Sometimes you just want to paste the result of a cmdlet into an Excel spreadsheet.
First you will probably try to copy the content from a Gridview :
get-process | out-gridview
But you will realise that the column’s headers are missing.
Why not try to pipe the result into the clip command and paste it in Excel :
get-process | clip
Well not so great, everything is in one big column (no delimiter).
Ok, let’s try to convertto-csv before doing the copy :
get-process | convertto-csv -Delimiter "`t" -NoTypeInformation | clip
Looks great … but if you happen to have any french characters, they will not appear in Excel.
So, my final solution is to re-encode the output in UTF8 by saving it to a file :
get-process | export-csv -Delimiter ";" -NoTypeInformation -force -path "$env:TEMP\export-csv.csv" -Encoding UTF8; start-process excel "$env:TEMP\export-csv.csv"
If you use the –Passthru parameter with Out-GridView,you will be able to select one or more row, click OK and send the result to the pipeline.
For example, if you wish to restart one or more services, try :
get-service | out-gridview -PassThru | restart-service -Verbose
$currentPrincipal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
$administratorsRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
if (!$currentPrincipal.IsInRole($administratorsRole)) {
Throw "This script must be run in an elevated session."
}
Function Get-HelloWorld {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Low")]
param()
$message="Hello world"
if ($PSCmdlet.ShouldProcess($message,"Write-host")) {
write-host $message
}
}Get-HelloWorld -whatif will produce :
What if: Performing operation "Write-host" on Target "Hello world". |
As a bonus, you will also be able to use the -confirm and –verbose parameter
You can use expressions with a select command to create custom calculated columns. Simply create an Hash table with 2 objects : name, expression.
Here’s a sample :
Get-ChildItem C:\ | Select-Object Name, length, @{Name="Kbytes";Expression={$_.Length / 1Kb}}In the good old days, with cmd.exe batchs you can easily add a PAUSE in your script to allow you press a key to continue.
In Powershell you have to use the ReadKey() method. The only bug is that you can’t use ReadKey() if your not in the console. That could be a problem if you use PowerGUI or PowerShellISE to develop and run your scripts.
Here’s a little function that will do the trick :
#.SYNOPSIS
# Emulation of PAUSE command (cmd)
#.DESCRIPTION
# Author : Jeffrey Snover [MSFT]
#.LINK
# http://blogs.msdn.com/b/powershell/archive/2007/02/25/pause.aspx
function Pause ([string]$Message="[Pause] Press Enter key to continue ...")
{
if (((Get-Host).Name) -eq 'ConsoleHost') {
Write-Host -NoNewLine $Message
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Write-Host ""
}
Else {
Read-Host -Prompt $message
}
}Thanks to Jeffrey Snover for this code.
Running an external command in powershell looks pretty easy, to run notepad just do :
notepad.exe
Things get a little be more complicated when you add many arguments with spaces and quotes, something like the following just wont work :
Winrm set winrm/config/listener?Transport=HTTP+Address=* @{Enabled="false"}Why is that ?
Powershell is a programming language, it has to evaluate (interpret) every line in a script as an expression. The preceding line is not a valid Powershell expression. Each arguments must be treated as an object (a String in this case), So a valid expression would be :
&Winrm "set" "winrm/config/listener?Transport=HTTP+Address=*" "@{Enabled=`"false`"}"As you can see this can rapidly become a nightmare when you add variables and path to that call. Really not easy to debug. This is why I wrote my top 5 tips for running external commands in Powershell.
Using the call operator (&) will help Powershell to evaluate the line as a command and not as an object. For example it will allow the following command to start Internet Explorer :
&"C:\Program Files\Internet Explorer\iexplore.exe"
Each string will be treated one argument. Put each argument in a different string et separate them with a space or even better put them in an array like this :
$CallArgs = @("set",
"winrm/config/listener?Transport=HTTP+Address=*",
"@{Enabled=`"false`"}"
)
&WinRM $CallArgsIf you want a quote to appear in an argument you must escape it like this : `"
Debugging the call operator can be pretty hard simply because you can’t see what is really passed as arguments to the external command by Powershel. EchoArgs.exe is a great little tool that will simply display the arguments and the command line used to call the program. So the following command :
&.\echoargs.exe winrm "set" "winrm/config/listener?Transport=HTTP+Address=*" "@{Enabled=`"false`"}"Will produce :
Arg 0 is <winrm>
Arg 1 is <set>
Arg 2 is <winrm/config/listener?Transport=HTTP+Address=*>
Arg 3 is <@{Enabled=false}>
Command line:
"I:\Mes Documents UL\PoSH\EchoArgs.exe" winrm set winrm/config/listener?Transport=HTTP+Address=* @{Enabled="false"}EchoArgs.exe is part of the Powershell Community Extensions (PSCX). You should download the version 2.1 (still in beta) to get the full comand line displayed. The Arguments (arg 0, arg 1, …) displayed by EchoArgs.exe seems to have been normalized (quotes have bnne strip), so the full command line is very helpfull.
Don’t forget to use Powershell dot-source syntax (.\) when you call an external command in the current directory :
&.\echoargs.exe
This one is quite easy, simply add a write-verbose command in your script :
write-verbose "The script is running !"
Then launch your script with the –verbose parameter :
.\myscript.ps1 -verbose
By default write-verbose output is not display because the default value of $VerbosePreference is set to SlientlyContinue. By adding the –verbose parameter, the value of $VerbosePreference is set to Continue (in the context of the script).
Sometimes you a have a string like ”Cannot find file $1” where you want to replace the $1 by a value (ex: myfile.txt). Sure you can use the –replace operator but there is a much more elegant solution : the –format operator.
$ErrMsg="Cannot find file {0}" -f 'myfile.txt'$ErrMsg will output :
Cannot find file myfile.txt
You can even use the –format in a multi-value, multi-line way :
$ErrMsg=@"
Filename : {0}
Filepath : {1}
Size : {2:N} MB
"@ -f 'Myfile.txt','C:\temp',(1234567/1MB)$ErrMsg will output :
Filename : Myfile.txt
Filepath : C:\temp
Size : 1,18 MB
$myString = @" This is the first line This the second line This is the third line "@
Note : the @” and the ”@ must be the last symbols on the line.
There isn’t a msgbox or an input function in Powershell, but you can directly call Windows.Forms.MessageBox to do it. Thansks to Bart De Smet for his tutorial about that.
[reflection.assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") | out-null
#.SYNOPSIS
# Display a confrimation dialog
# Return$True if the user click 'Yes'
#.LINK
# http://bartdesmet.net/blogs/bart/archive/2006/09/16/4429.aspx
function ConfirmMessageBox {
param(
[parameter(
Mandatory=$False)]
[String]$WinTitle='PowerShell Script',
[parameter(
Mandatory=$False)]
$MsgText='Do you really want to continue ?'
)
$result = [Windows.Forms.MessageBox]::Show($MsgText, $WinTitle, [Windows.Forms.MessageBoxButtons]::YesNo, [Windows.Forms.MessageBoxIcon]::Question)
If ($result -eq [Windows.Forms.DialogResult]::Yes) {
Return $true
}
Else {
Return $false
}
}In VBscript you can define a function using a ByRef argument like this :
Function CommentLine (ByRef arg)
In PowerShell you have to use the [ref] type to pass the reference of a variable instead of its value.
Here’s a example of how to define and call a function using a [ref] argument :
function CommentLine([ref]$arg) {
$arg.value = "# " + $arg.value
}
$Line="This should be a comment"
CommentLine ([ref]$Line)
$lineDon’t forget your passing the reference of the object, you have to use the value property of the reference to access the variable or object itself.
When you want to access the class from an assembly you have to load it. For those of you familiar with .net development (Visual Studio) it is the equivalent of adding a reference to an assembly in you project and adding an import statement like this :
imports Microsoft.UpdateServices.Administration
In powershell you do it this way :
[reflection.assembly]::Load("Microsoft.UpdateServices.Administration, Version=3.1.6001.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35")Please note that you should use the complete assembly display name. You can get all that info (name, version, token) from the GAC (c:\windows\assembly) when you display the properties of an assembly.
You should not use the LoadWithPartialName method, it is obsolete. Read Suzanne Cook's .NET CLR Notes to understand why.
Normally when you call a script it launch in a new scope. The trick is to use “dot sourcing”, this will force the script to launch in the same scope.
So in your script you can :
. .\Myfunctions-Library.ps1
Frenquently asked questions about everyday real problems in PowerShell