Tag Archives: powershell

PowerCLITools Community Module: Now on GitHub

Over the last few years I have built up a number of functions to use alongside the out of the box functionality in PowerCLI. I’ve posted some of the content before on this blog, but have at last got round to publishing all of the content that I am able to share, in a module available on GitHub – I’ve named it the PowerCLITools Community Module in the hope that some others might want to contribute content to it or improve what I have already put together.

octobiwan

This took a fair amount of effort since it is not possible for me to share everything that I have as part of my locally stored version of this toolkit. Some of it was developed by others I was working on projects with (and are not as necessarily so keen to share certain parts of their work) and some can’t be shared for commercial reasons. However, I found some time recently to split out everything that could be shared into a new module and also updated some of the code – typically to add some nice features in PowerShell v3 and later which weren’t available when a lot of the code was developed during PowerShell v2 days.

Since the content has been developed over a few years, consistency and standardisation of approach may not be 100% there. A quick look back over them showed some looking a bit dated – I have spent a bit of time tidying them up, but part of the reason for sharing them  was to take feedback and some prompting on where they could be improved. If I left them until I thought they were just right I’d probably never end up publishing them. So your feedback is the impetus I need to go and improve them :-)

A lot of the functions are there to fill in gaps in cmdlet coverage with PowerCLI and there are a few which I made more for convenience where I have bundled together a few existing cmdlets into one function. These don’t particularly add a lot of value, but maybe demonstrate how you can tighten up your scripts a bit

Pre-Requisites

Ensure that VMware PowerCLI is installed. Functions have been tested against v5.8 R1.

Installation

1) Download all files comprising the PowerCLITools module. Ensure the files are unblocked and unzip them.
2) Create a folder for the module in your module folder path, e.g. C:\Users\username\Documents\WindowsPowerShell\Modules\PowerCLITools
3) Place the module files in the above folder

So it should look something like this:

PowerCLIToolsModule01
Usage

The below command will make all of the functions in the module available


Import-Module PowerCLITools

To see a list of available functions:


Get-Command -Module PowerCLITools

Add-vCenterLicense
Get-ClusterAverageCpuMemory
Get-CurrentVIServer
Get-SnapshotCreator
Get-vCenterLicense
Get-VMCreationDate
Get-VMDiskData
Get-VMHostAlarm
Get-VMHostDumpCollector
Get-VMHostiSCSIBinding
Get-VMHostLicense
Get-VMHostNetworkAdapterCDP
Get-VMHostSyslogConfig
Get-VMIPAddressFromNetwork
Get-VMSCSIID
Install-vSphereClient
Install-vSpherePowerCLI
New-vCenterPermission
New-vCenterRole
New-VMFromSnapshot
Remove-vCenterLicense
Set-VMHostDumpCollector
Set-VMHostiSCSIBinding
Set-VMHostLicense
Set-VMHostSyslogConfig
Set-VMHostToCurrentDateandTime
Test-VIServerConnection
Update-ESXiSSL
Update-VMNotesWithOwner
Update-VMScsiDeviceOrder

Nested Modules

You will note that each function is itself a nested module of the PowerCLITools module. In this blog post I describe why I make my modules like this.

VI Properties

If you take a look inside the PowerCLITools.Initialise.ps1 file you’ll notice a number of VI Properties. Some of these are required by some of the functions in the module and some are just there for my convenience and make using my PowerCLI session simpler. You can add and remove VI Properties as to your own personal preference, but watch out that some are actually needed.  You can find out more about VI Properties here.

 Feedback

I really hope people find these functions useful. I have a number of ideas on where some can be improved, but please provide your own feedback as it’ll be the nudge I need to actually go and make the changes :-)

Get-Task: ID Parameter is Case Sensitive

There aren’t many occasions when you trip up in PowerShell because of something being case sensitive, it generally doesn’t happen since most things are typically not like that. I was working with the PowerCLI cmdlet Get-Task and in particular the ID parameter to do something like:


Get-Task -Id 'task-task-2035'

I had originally found the ID via:


Get-Task | Format-Table Name,ID -AutoSize

However, I received the error that no tasks of that ID were found :

Get-Task : 24/02/2015 20:51:57 Get-Task The identifier task-task-2035 resulted in no objects.
At line:1 char:1
+ Get-Task -Id task-task-2035
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-Task], VimException
+ FullyQualifiedErrorId : Client20_OutputTracker_ReportNotFoundLocators_LocatorNotProduced,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetTask

Turned out that making the task ID match the exact case worked:


Get-Task -Id 'Task-task-2035'

Apparently the IDs are case sensitive by design :-)

One to watch out for anyway…..

PowerCLI is now a Module!

We’ve been waiting for this a long time, but with the 6.0 release PowerCLI is now available as a module. Microsoft changed the way itself and third-parties should deliver PowerShell functionality back in PowerShell version 2 by offering modules. Previously in PowerShell version 1 additional functionality was available via snap-ins.

It’s not fully there yet, but some of the functionality is now available in a module. 6.0 will be a hybrid release, with the rest to follow later.

Notice how the Core functionality is in both lists since this is a hybrid release.


Get-Module *vmware* -Listavailable

PowerCLIModule01


Get-PSSnapin *vmware* -Registered

PowerCLIModule02

I believe there was significant effort in making this leap, so many thanks to Mr Renouf and his team :-)

How To Make Use Of Functions in PowerShell

Over the last few weeks I’ve had a number of comments on posts essentially asking the same question: “How do I use the functions that you publish on your blog?”. So I thought it worth making a post to refer people to, rather than trying to respond in kind to each comment. There are a number of ways it can be done depending on your requirements and they are listed below.

First of all, let’s create a simple function to use for testing:


function Get-TimesResult {

Param ([int]$a,[int]$b)

$c = $a * $b

Write-Output $c
}

1) Paste Into Existing PowerShell Session

If you are working interactively in the console then the function can be copy / pasted into that session and is then available for the duration of that session. I find this easier to do via the PowerShell ISE than the standard console.

Copy the function into the script pane:

Functions01

Click the Green Run Script button or hit F5 and the code will appear in the console pane:

Functions02

The function is now available for use and if using the ISE will appear interactively when you start typing the name:

Functions03

Functions04

 

2) PowerShell Profile

If the function is something that you wish to use regularly in your interactive PowerShell sessions then you can place the function in your PowerShell Profile and it will be available every time you open your PowerShell console.

If you are unsure what a PowerShell profile is or how to use one, there is some good info here. A quick way to create one is:


New-Item -Path $profile -ItemType File -Force

Once you have created a PowerShell profile, place the function in the profile and save and close. Now every time you open your PowerShell console the function will be available.

Functions05

3) Directly In A Script

If you wish to use the function in a script, place the function in the script above the sections where you need to use it. Typically this will be towards the top. The plus side of doing it this way is everything is contained in one file, a negative is that if you have a number of functions then readability of the script is reduced since there may be a long way to scroll down before anything of significance starts to happen.

Functions06

4) Called From Another Script

One method I have seen quite often in the wild (and I’m not a particular fan of, point 5 is a much better approach) is to store all regularly used functions in a script file and dot source the functions script file in the script where you need to use one or more of the functions.

Functions script file Tools.ps1:

Functions07

Get-Results script file calling Tools.ps1:

Note the dot and a space before the reference to the Tools.ps1 file


. C:\Users\jmedd\Documents\WindowsPowerShell\Scratch\Tools.ps1

Get-TimesResult -a 6 -b 8

Functions08

 

5) Stored in a Module

Using a PowerShell module is a more advanced and significantly more structured and powerful method of achieving what was done in 4). If you haven’t used PowerShell modules before I wrote an introduction to PowerShell modules a while back which you can find here.

Essentially they are a method to package up your reusable functions and make them available in a manner similar to how other teams in Microsoft and third-parties produce suites of PowerShell cmdlets for consumption.

For this example I have created a Tools module to use, which essentially is the same content as the Tools.ps1 file, but stored in a *.psm1 file (Tools.psm1) in the Modules\Tools folder on my workstation.

Note: the name of the *.psm1 file should match that of the folder. Its possible to create a more enhanced module than taking this approach using a Module Manifest, but we don’t need that for the purposes of this post. It’s described further in the previously mentioned article.

Functions09

Now we can use the *-Module PowerShell cmdlets to work with our content.

To observe the module we can use Get-Module:


Get-Module Tools -ListAvailable

Functions10

To use the functions contained in  the module we can use Import-Module


Import-Module Tools

Get-TimesResult -a 6 -b 8

 

Functions11

Note: Since PowerShell v3 automatic cmdlet discovery and module loading has been supported. (You can find out more about it here) Consequently, you don’t actually need to use Import-Module to get access to the functions as long as you place the Module in the correct location. However, it would be a good practice to add the Import-Module line to your script, so that another user is aware of where you are getting the functionality from.

Improving the PowerShell ISE Experience with ISESteroids 2

For a long time I’ve used the built-in to Windows, PowerShell ISE for my PowerShell scripting experience. Most people tend to have a particular favourite editor for their coding, usually after trialling out a few different ones. For pretty much everything else I’ve settled on Sublime Text, but for PowerShell I use the ISE since I really like the integration with the PowerShell console.

The ISE was introduced in PowerShell 2.0 and to be honest was pretty basic back then. It’s  improved significantly since then into version 4, but still has some areas where there could be improvement or features missing that you would like to see.

Earlier in the year I tried out ISESteroids 1.0 which started to plug a number of the gaps I found in the ISE. Recently I had chance to upgrade to ISESteroids 2 and it has improved even further.

For a quick preview of what is available check out the below video.

A few things in it I particularly like:

1) More distinct (yellow) highlighting of bracket matching or other sections

ISESteroids01

(single click on this double quoted string)

ISESteroids02

2) Block commenting (this was a real annoyance for me – there is a keyboard shortcut to do it in the standard ISE, but still fiddly)

Before:

ISESteroids03

After pressing the single button below:

ISESteroids04

ISESteroids05

 

3) ScriptMap which allows you to easily navigate your way through long scripts

ISESteroids06

 

4) Manage Version History

ISESteroids07

Clicking on Compare opens WinMerge and a view of what has changed between versions

ISESteroids08

5)  Autoselection. Click repeatedly to select various code segments

ISESteroids12

ISESteroids09

ISESteroids10

ISESteroids11

 

6) Enhanced Debugging

Best explained in the following video

For a more in-depth look at some of the features, check out the below video with ISESteroids creator Dr Tobias Weltner and fellow PowerShell MVP Jeff Wouters.

Automating Disk Zeroing on VM Deletion

A requirement for a project I had was to zero the VMDK of all VM disks at the time of VM removal.

smash_hard_drive

One consideration was to SSH into the host where the VM was located and use vmkfstools like the below on each vmdk to zero the disk.

vmkfstools –w /vmfs/volumes/<…>.vmdk

Looking for alternatives I found that the PowerCLI cmdlet Set-HardDisk has a ZeroOut parameter. Note the text from the help (version 5.8 R1):

Specifies that you want to fill the hard disk with zeros. This parameter is supported only if you are directly connected to an ESX/ESXi host. The ZeroOut functionality is experimental.

The points to note are:

  • You will need to connect PowerCLI directly to the ESXi host that the VM is registered on. So you will most likely first of all need to connect to vCenter to find out where the VM lives.
  • The functionality is ‘experimental’. A quick scan back through releases showed this had been the same for some time. From my observations the functionality appeared to work fine. There have been many things in vSphere over the years which have been ‘experimental’, but have usually worked fine.

So once you have identified where the VM is and connected to the ESXi host in question, it’s a case of simply looping through all of the disks and zeroing them (with a bit of logging thrown in) – note it will likely take a fair amount of time to zero each disk!


$VM = VM01
$HardDisks = Get-HardDisk -VM $VM

foreach ($HardDisk in $HardDisks){

$HardDisk | Set-HardDisk -ZeroOut -Confirm:$false | Out-Null

$Text = "Zeroed disk $($HardDisk.Filename) for VM $VM"
$Text | Out-File -FilePath C:\log\zerodisk.log -Append
}

Calling PowerShell.exe -Command ScriptName and Parameters with Commas

Bit of an obscure one this, but I hit it recently and wasted some time on it so I thought it might be useful for someone, somewhere, someday.

If you need to call a PowerShell script via a command line style prompt (maybe in a scheduled task or an external system like vCenter Orchestrator) there are a number of different options.

I was troubleshooting a problem where an existing system was failing with a command along the lines of this:


C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command C:\Scripts\TestComma.ps1 -input1 'banana,pear'

and would fail with the following error:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe : C:\scripts\TestComma.ps1 : Cannot process argument transformation on parameter
At line:1 char:1
+ C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command C:\scripts\Te …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (C:\scripts\Test…n on parameter :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

‘Input1′. Cannot convert value to type System.String.
At line:1 char:34
+ C:\scripts\TestComma.ps1 -input1 banana,pear
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [TestComma.ps1], ParameterBindi
ngArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,TestComma.p
s1

PowerShellCommandComma01

So it looked like it was having an issue with the string being supplied as the parameter ‘banana,pear’ even though there is normally no issue with this being a string. I eventually tracked it down to being a problem with the comma – with no comma there is no issue.

Note: This is only an issue when being called by powershell.exe. When used in a standard PowerShell console or script there is no issue with this text being a string:


('banana,pear').GetType()

PowerShellCommandComma05

There are a number of ways round this:

1) Run it from cmd.exe

Sacrilege I know, but the system I was working with effectively was calling it from cmd.exe which subsequently didn’t experience the issue.

PowerShellCommandComma02

 

2) Escape the comma

Escape the comma character like so


C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command C:\scripts\TestComma.ps1 -input1 'banana`,pear'

PowerShellCommandComma03

3) Use the File parameter instead

The better solution in my opinion is to use the File parameter. I typically use this anyway rather than the Command parameter. It was introduced in PowerShell v2 and has been my preferred way of doing this kind of thing since then.


C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\scripts\TestComma.ps1 -input1 'banana,pear'

PowerShellCommandComma04

 

Start-Transcript Now Available in the PowerShell ISE in PowerShell v5

*Warning. This article was written using the September 2014 PowerShell v5 Preview*

PowerShellZip01

Prior to PowerShell v5 it was not possible to use Start-Transcript in the PowerShell ISE, it could only be used in the standard PowerShell console. You would receive the error:


Start-Transcript : This host does not support transcription.

PowerShellStartTranscript01

(There were alternatives to get round it , and here)

Now in PowerShell v5 it can be used natively:


Start-Transcript -Path C:\Test\Transcript.txt
Get-Service
Stop-Transcript

Start-Transcript

PowerShellStartTranscript02

Run your commands (in this example just one, Get-Service). Then Stop-Transcript

PowerShellStartTranscript03

Now view the transcript  file


notepad C:\Test\Transcript.txt

PowerShellStartTranscript04

 

Getting Zippy with PowerShell v5

*Warning. This article was written using the September 2014 PowerShell v5 Preview*

PowerShellZip01

 

(OK, I was really looking for an excuse to use the below picture in a blog post)

Zippy

 

One of the most popular and long standing requests for PowerShell is native support for working with Zip files. With PowerShell v5 we get two new cmdlets Compress-Archive  and and Expand-Archive. Here’s a couple of examples of how they work.

Compress-Archive

1) Create a Zip file

C:\Test contains a number of text files. We want to zip them up into one convenient file.

PowerShellZip02

 


Compress-Archive -Path C:\Test\* -DestinationPath C:\Zip\Test.zip -CompressionLevel Optimal

and now we have the zip file:

Note: as of this release there are three Compression Levels, the default of which is Optimal.

 

PowerShellZip04

PowerShellZip05

2) Update a Zip file

Now we add an extra file to C:\Test and want to update the zip file with this new file

 

PowerShellZip06


Compress-Archive -Path C:\Test\* -DestinationPath C:\Zip\Test.zip -Update

Here’s the new file, now contained in the zip file:

PowerShellZip07

Expand-Archive

3) Expand a Zip file

Now we want to expand a zip file. Let’s use the one we just created and expand it to a different folder C:\Expand.


Expand-Archive -Path C:\Zip\Test.zip -DestinationPath C:\Expand

Here are the files:

PowerShellZip08

All pretty straightforward, but it’s great to have this simple functionality finally native :-)

 

Setting Static Routes with PowerShell when connecting to a PPTP VPN

Sometimes as a consultant I have a need to connect to customer or client networks to carry out some of the work. This typically involves a myriad of different remote connection and VPN style systems. Some are better than others and while it’s possible to use different VMs to connect to them, that’s not always practical. Typically I only want traffic destined for the remote system(s) to go down the VPN, not all of my Internet traffic.

Many reasons for this, but one of the top ones is that it sends my Lync client used for internal communication into a frenzy of disconnecting / re-connecting to conversations if the VPN connection drops any time during the day. This leads to timed out messages and half the time wondering if the message got through, whether to send it again and generally a pretty frustrating experience.

One of the VPN connections I need to use is pretty basic and uses a PPTP connection created via the built-in wizard in Windows.

VPN01

I hadn’t used one of these for a long time and thankfully a colleague pointed out to me the other day that by changing it’s configuration it was possible not to send all of your Internet traffic down it.

Clearing the below setting Use default gateway on remote network will stop all Internet destined traffic heading down that connection.

VPN02

 

Then we simply need to set a static route for the subnet we want to connect to via the VPN and send it down that route. So it will be something like:

route add 172.15.36.0 mask 255.255.0.0 172.100.25.37 metric 1

However, the IP I’m allocated from the VPN server (172.100.25.37 above) may change every time I connect to the VPN.

VPN03

So I put together the below function which will grab the IP that has been allocated and use it in the route add command. Since I wanted to support downlevel OSs for people like me using Windows 7 I went with ipconfig to get this info rather than than the newer networking cmdlets like Get-NetIPAddress . Consequently, I used this really handy tip on filtering ipconfig output.

Then all I need to do is run the following (note: make sure your PowerShell session has elevated privileges):


Set-VPNRoute -VPNNetwork 172.100.25 -RouteNetwork 172.15.36.0 -RouteMask 255.255.0.0


function Set-VPNRoute {
<#
 .SYNOPSIS
 Set a route for VPN traffic

 .DESCRIPTION
 Set a route for VPN traffic

 .PARAMETER VPNNetwork
 VPN Connected Network

 .PARAMETER RouteNetwork
 Target Route

 .PARAMETER RouteMask
 Target Mask

.INPUTS
 System.String.

 .OUTPUTS
 None.

.EXAMPLE
 PS> Set-VPNRoute -VPNNetwork 192.168.200 -RouteNetwork 192.168.60.0 -RouteMask 255.255.255.0

#>
[CmdletBinding()]

Param
 (

[parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [String]$VPNNetwork,

 [parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [String]$RouteNetwork, 

 [parameter(Mandatory=$true)]
 [String]$RouteMask
 )

try {

 $VPNIP = @(ipconfig) -like "*$VPNNetwork*"
 $VPNIP = $VPNIP[0].substring($VPNIP[0].length - 14, 14)
 route add $RouteNetwork mask $RouteMask $VPNIP metric 1 | Out-Null
 }
 catch [Exception]{

 throw "Unable to set VPN Route"
 }
}