Archive for the ‘scripting’ Category
How to get $VerbosePreference applied on remote commands
If you use Write-Verbose in a script executed on a remote computer, you may not get the result you expect.
Invoke-Command -ComputerName comp1 -ScriptBlock {
Write-Host "hello"
Write-Verbose "there"
}
I thought I was clever by just passing along the $VerbosePreference value:
Invoke-Command -ComputerName comp1 -ArgumentList $VerbosePreference -ScriptBlock {
param( $VerbosePreference )
Write-Host "hello"
Write-Verbose "there"
}
But, still no luck! Only the statement using Write-Host is included in the output when the command is invoked on the remote computer.
What’s going on here is that the argument sent to the remotely executed script block is converted to an integer, which is the base integral type of the underlying enum type of $VerbosePreference.
So I figured that typing the argument in the param() clause will bind the parameter to the correct data type, and voila, it now works as expected.
Invoke-Command -ComputerName comp1 -ArgumentList $VerbosePreference -ScriptBlock {
param( [System.Management.Automation.ActionPreference] $VerbosePreference )
Write-Host "hello"
Write-Verbose "there"
}
Now you can toggle the verbose output of your remote script using the switch of your local script:
.\TestRemoteCommand.ps1 # Default, or -Verbose:$false .\TestRemoteCommand.ps1 -Verbose
Enable-PSRemoting is broken in PowerShell?
Yesterday I installed the RTM bits of WinRM 2.0 on a Windows Server 2003 machine and it were really no issues at all getting it working.
Pimping PsUnit with constraint based assertions
Using PsUnit one can set up scripted “unit tests” for PowerShell scripts. I have sort of just started using PsUnit at work and right now I feel that a lot of the basic features of other test frameworks are missing.
<![CDATA[
$result = DoSomething
Assert-That $result { $ActualValue -eq "expected value" }
]]>
Using NUnit and this constraint based model, a simple assertion can look like this:
<![CDATA[
Assert.That( myString, Is.EqualTo("Hello") );
]]>
I quite don’t like the “barebone” scriptblock you need to write to use Assert-That in PsUnit, so here is what I came up with (using my example of above):
<![CDATA[
$result = DoSomething
Assert-That $result (IsEqualTo "expected value")
]]>
I also added another constraint which allows you to assert that some code throws something:
<![CDATA[
# The following assertion will always pass
Assert-That { throw "blah!" } (ThrowsException)
]]>
This complements the already built-in feature of PsUnit which allows you to add a parameter, $ExpectedException, to the test. If you have been using MSTest you would use the method attribute ExpectedException for this.
Here is another example which tweaks the constraint to a somewhat more specific type of exception:
<![CDATA[
# The following assertion will fail since throw in PowerShell creates a System.Management.Automation.ErrorRecord
Assert-That { throw "blah!" } (ThrowsException -Exception $([System.ArgumentException]))
]]>
A more usable test using the ThrowsException constraint could be:
<![CDATA[
Assert-That {
# The following web resource does not exist, thus an error should be the result
Get-Http "http://blogger.com/asdfg"
} (ThrowsException -Exception $([System.Net.WebException]))
]]>
The biggest improvement using these new constraints is the output in the test result.
While using PsUnit out-of-the-box…
<![CDATA[
$foo = "bar"
Assert-That $foo { $ActualValue -eq "foo" }
]]>
…results with “Assert-That returned false!” in the test result.
Using the constraint in this blog post…:
<![CDATA[
$foo = "bar"
Assert-That $foo (IsEqualTo "foo")
]]>
…results with “ActualValue ‘bar’ is not equal to expected value ‘foo’“.
The same level of detail is provided by the ThrowsException constraint, which may result in “No exception was thrown, expected exception of type ‘System.ArgumentException’“.
Any ideas, suggestions etc.? Please don’t hesitate to drop a comment!
Integrating PsUnit and MSBuild
The last month I have been digging into, adding new and refactoring the Powershell scripts we use in RemoteX Applications. We primarily use PowerShell to configure and deploy our product.
<![CDATA[
<PropertyGroup>
<CoreTestDependsOn>$(CoreTestDependsOn);RunPsUnit</CoreTestDependsOn>
<RunPsUnitDependsOn>CoreRunPsUnit</RunPsUnitDependsOn>
</PropertyGroup>
<Target Name="RunPsUnit" DependsOnTargets="$(RunPsUnitDependsOn)"/>
<Target Name="CoreRunPsUnit" Condition=" '$(RunTest)'!='false' ">
<Exec Command="$(PowerShell) -Command "&{ .\runpsunit.ps1 -PsUnitTestFile %(PsUnitTest.FullPath) }""/>
</Target>
]]>
Using this batch target, all that needs to be done is to add PsUnitTest items to the build script. Just like the way we set up TestContainer items for use with MSTest:
<![CDATA[
<ItemGroup>
<PsUnitTest Include="$(SolutionRoot)\Scripts\Tests\*.Test.ps1"/>
<PsUnitTest Include="$(SolutionRoot)\References\PsUnit\*.Test.ps1"/>
</ItemGroup>
]]>
So far so good, but when tests are failing, the build is successful. This is because the Exec task only reports a build error when the executed command returns an exit code other than zero (0×0). So I started looking into the test runner of PsUnit (PsUnit.Run.ps1) and discovered that it didn’t send any output nor produced some other exit code than 0×0. The only output PsUnit will send you is some nice colorful information written to the PowerShell host.
<![CDATA[
$TestResults
]]>
This makes PsUnit.Run.ps1 send the test results along the pipeline.
<![CDATA[
$results = Invoke-Expression "$psUnitRun -PsUnitTestFile $PsUnitTestFile"
$results | % {
if( $_.Result -eq "FAIL" ) {
throw "Test run failed: $($_.Test) - $($_.Reason)"
}
}
]]>
This makes the Exec task report a build error if we find any failing test in the test result from PsUnit – and last but not least – the name of the failing test and the reason ends up nicely in the MSBuild log file.
Yay, PowerShell v2 is finally RTMed!
PowerShell v2 is finally RTMed for the rest of us who still has a couple of boxes not running Windows 2008 Server R2 or Windows 7.
PowerTip of the day – Variable descriptions
I just got this in my inbox from Powershell.com:
Add Descriptions to Variables
Keeping track of a variable’s purpose can be accomplished by assigning a clear text description:
$ip= '10.10.10.10' Set-Variable ip -description 'Server IP or Name'
When you now list your variables, you can output the variable description as well:
dir variable:ip | Format-Table Name, Value, Description
Playing around a little I found out that you easily can get the same information about system/environment variables using Get-Variable and Format-Table:
gv | ft Name, Description Name Description ---- ----------- $ ? Execution status of last command. ^ _ args bla Bla bla bla ConfirmPreference Dictates when confirmation should be... ConsoleFileName Name of the current console file. DebugPreference Dictates action taken when an Debug ... Error ErrorActionPreference Dictates action taken when an Error ... ErrorView Dictates the view mode to use when d...
PowerShell Scripting Guidelines
Lately I have been doing some work related to how we automate the process of how we install, configure and deploy our product into a server environment. Since our pre-requisites are Windows Server 2008 and Windows 7, we can totally go crazy with PowerShell scripts. Next thing to sort out after upgrading to Windows Server 2008 R2 is of course PowerShell Remoting..
However, using quite an amount of PowerShell scripts, we need some guidelines to structure them. This to complement our guidelines covering OO design and C# code style.
So I have started out with the following draft and I’m looking for some tips and comments about this and what you (yes, you!) believe is a good rule of thumb to use when developing and maintaining a bunch of PowerShell scripts.
1 Scripting Guidelines
When there is a need of a script, use your mighty PowerShell skills!
Here is a couple of guidelines which should be followed to speed up development, maintenance and usage of PowerShell scripts.
Apply the above mentioned principles when applicable.
1.1 Function files
Package common functions into function files and dot-source them in the script which needs them.
Example:
. ..\DB\Common.ps1
1.2 File names
Name PS1 files using standard verbs like get, set, remove, new etc., or a verb that fits the purpose or the script. Do not use verbs in file names for files that have generic purpose, like a function library file. Also consider using PowerShell v2 modules when applicable.
Scripts that are to be called from the prompt are easier to understand if they are named in such way that no “usage” or get-help is needed when invoking the script. Like add-user.ps1, remove-database.ps1 and so on.
Avoid complex scripts with multiple purposes which becomes complex with parameter sets (get-help about_functions_advanced_parameters)
1.3 Script and function parameters
Use built-in support to handle function and script parameters. Avoid index-based unnamed parameters using $args.
Reference: get-help about_arguments; get-help about_functions; get-help about_functions_advanced_parameters
MSDN: http://msdn.microsoft.com/en-us/library/ms714433(VS.85).aspx
Example, testme.ps1:
param(
[Switch] $ver = [Switch]::Present
)
Write-Host $args
Write-Host “Testing!”
if( $ver )
{
Write-Host “Testing even more!”
}
Output:
PS > .\testme.ps1 –ver
PS > .\testme.ps1 -ver:$false
1.4 Decorate function parameters appropriately using validation rules
Decorate your parameters with validation rules and help messages to avoid tedious work on custom “script usage” functions. Your script users will be able to use “get-help yourscript.ps1”, or the equivalent “yourscript.ps1 -?” to get script usage information. Users will also be prompted if mandatory parameters are left out blank.
Get-help will also print out common parameters (get-help about_commonparameters).
Example:
param(
[Switch] $ver = [Switch]::Present,
[Parameter(mandatory=$true, helpmessage="Your name")]
[ValidateNotNullOrEmpty()]
[string] $name = “John Doe”
)
Write-Host $args
Write-Host “Hello $name!”
if( $ver )
{
Write-Host “Your name is $($name.Length) characters long”
}
Output:
PS > get-help .\testme.ps1
testme.ps1 [-name] [-ver] [-Verbose] [-Debug] [-ErrorAction ] [-WarningAction ] [-ErrorVariable ] [-WarningVariable ] [-OutVariable ] [-OutBuffer ]
1.5 Document script usage using PowerShell comment based help
When it is necessary to document how a script can be used, or when you want to provide more information about your script’s parameters than just the name, document it using the PowerShell help syntax. This can be compared to using XML comments in languages such as C#/VB or javadoc tags in Java.
See “get-help about_comment_based_help” for more information.
Example:
<#
.Synopsis
Synopsis text here
.Description
Usage description here
.Parameter ver
Enabling this switch prints out the length of the given name
.Parameter name
The name to say hello to
#>
param(
[Switch] $ver = [Switch]::Present,
[Parameter(mandatory=$true, helpmessage="Your name")]
[ValidateNotNullOrEmpty()]
[string] $name = “John Doe”
)
Write-Host $args
Write-Host “Hello $name!”
if( $ver )
{
Write-Host “Your name is $($name.Length) characters long”
}
Output:
PS> get-help -full .\testme.ps1
NAME
C:\temp\testme.ps1
SYNOPSIS
Synopsis text here
SYNTAX
C:\temp\testme.ps1 [-ver] [-name] []
DESCRIPTION
Usage description here
PARAMETERS
-ver []
Enabling this switch prints out the length of the given name
Required? False
Position? Named
Default value
Accept pipeline input? False
Accept wildcard characters?
-name
The name to say hello to
Required? True
Position? 1
Default value
Accept pipeline input? False
Accept wildcard characters?
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
“get-help about_commonparameters”.
INPUTS
OUTPUTS
RELATED LINKS
1.6 Use validation rules to perform parameter input validation
PowerShell has several built-in validation rules for input validation. Use them as long as they add more value than doing custom validation using if-statements.
The built-in validation rules are: AllowNull, AllowEmptyString, AllowEmptyCollection, ValidateCount, ValidateLength, ValidatePattern, ValidateRange, ValidateScript, ValidateSet, ValidateNotNull, ValidateNotNullOrEmpty
See “get-help about_functions_advanced_parameters” for further reference.
1.7 Putting the right stuff on the pipeline
· Use write-host for console information with fancy colors
· Use write-output to be able to pipe your script/function to another script/function.
Example pipeline scenario, sending a user from add-user to new-guestbookentry:
. .\guestbooks-functions.ps1;
add-user “foo” “password” | new-guestbookentry “Welcome to our guestbooks.com!”
· Avoid unwanted objects on the pipeline using termination with $null assignments or piping the output to NULL, example:
add-user “foo” “password” # put the added user on the pipeline
$null = add-user “foo” “password” # added user object “terminated”
add-user “foo” “password” | out-null # added user object piped to NULL instead of the output
Note to self: Windows is getting sensitive with the IIS PowerShell provider
Today I have struggled with some of the most basic automation tasks with the new IIS 7 web server; Creating a web site, application pool and application using a PowerShell script.
Starting out with this tutorial I managed to use the following sample to accomplish my task:
New-Item IIS:\Sites\DemoSite -physicalPath C:\DemoSite -bindings @{protocol=”http”;bindingInformation=”:8080:”}Set-ItemProperty IIS:\Sites\DemoSite -name applicationPool -value DemoAppPool
New-Item IIS:\Sites\DemoSite\DemoApp -physicalPath C:\DemoSite\DemoApp -type Application
Set-ItemProperty IIS:\sites\DemoSite\DemoApp -name applicationPool -value DemoAppPool
Everything worked fine with the samples and I started to setup a testing environment on my machine. That involved setting up a local IIS 7, creating some directories in the file system for my test website etc.
All tasks seemed to work, but I repeatedly got the following error when trying to assign the correct application pool to the application:
Set-ItemProperty : Value cannot be null.
Parameter name: path
At C:\.....\WebSetup.ps1:37 char:17
+ Set-ItemProperty <<<< IIS:\Sites\DemoSite\Server -name applicationPool -value DemoAppPool
I tried to use other cmdlets to change the application pool setting, but with no success. I also tried to change all paths, the name of the web site etc. according to the sample. The sample code worked, but the error remained when executing Set-ItemProperty in my script.
I doubled checked the documentation, the sample, the value given to the parameter -Path – but it still failed.
I was just going to post a question about this matter in some forums when I figured it all out.
The name of the directories I created in the file system was using lowercase characters, but not the entries in the IIS configuration!
The cause of this must be the code in either the file system provider or the IIS provider for PowerShell. Some part of the code which is executed when invoking the Set-ItemProperty cmdlet is doing some case sensitive work..
The lesson and recommendation after this experience is that I will try to create both my entries in the file system and in the IIS configuration from the same script.
Windows is definitely getting more and more sensitive these days…