Tag Archives: powershell

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"
 }
}

Automating vCAC Tenant Creation with vCO: Part 3 Install the vCAC plugin for vCO

In this series we will see how to automate the creation of a tenant in vCAC using vCO. There are multiple tasks to provision a tenant in vCAC, so even though it is an automation product itself, there’s no reason why you shouldn’t look at automating parts of it too. In part 3 we look at installing the vCAC plugin for vCO

1) Download the vCAC plugin   o11nplugin-vcac-6.0.1.vmoapp vCOADPlugin40

2) Install the plugin I’m installing this on a Windows based vCO box. Ensure that the vCO Configuration service is started since it is usually on manual startup. vCOADPlugin41

Navigate to the Configuration webpage, in my case https://localhost:8283/

vCOvCACPlugin01

and then Plugins

vCOvCACPlugin02

Enter credentials of a member of the vCO admins group. (If you haven’t set this up you might want to add an AD connection on the Authentication page)

vCOvCACPlugin03

and select the downloaded plugin, then Upload and install

vCOvCACPlugin04

Accept the License Agreement

vCOvCACPlugin05

Hopefully you get a nice green success

vCOvCACPlugin06

If so, you’ll get a note further down that you need to restart the vCO Server service

vCOvCACPlugin07


Get-Service VMwareOrchestrator | Restart-Service

After the restart, all is now OK

vCOvCACPlugin08

The built-in vCAC workflows are now available in the vCO client

vCOvCACPlugin09

3) Configure the plugin Navigate to Configuration and run the Add a vCAC host workflow

vCOvCACPlugin10

Fill out the details of the default vCAC tenant

vCOvCACPlugin11 vCOvCACPlugin12

…and now we have a vCAC server to work with

vCOvCACPlugin13

 

Automating vCAC Tenant Creation with vCO: Part 1 AD SSL
Automating vCAC Tenant Creation with vCO: Part 2 AD Users, Groups and OUs
Automating vCAC Tenant Creation with vCO: Part 3 Install the vCAC plugin for vCO
Automating vCAC Tenant Creation with vCO: Part 4 Creating a Tenant
Automating vCAC Tenant Creation with vCO: Part 5 Creating an Identity Store
Automating vCAC Tenant Creation with vCO: Part 6 Adding Administrators
Automating vCAC Tenant Creation with vCO: Part 7 Creating a vCAC Catalog Item

I’ll be presenting some automation at the June 2014 South West VMUG

VMUG

Those great guys down in the South West of England, @mpoore, @jeremybowman,  @virtualisedreal and @simoneady have kindly invited me down to their next VMUG to present about automation. So I will be talking about some of my experiences in automation projects from the last few years and particularly how to write your own code in a generic way so that it is portable across different projects and systems.

It looks like there is plenty of other good content lined up that day so I’d suggest you get down there too.

VMUG02

 

 

 

Using Git, Stash and Dropbox to Manage Your Own Code

Sometimes I’m asked how I manage my own (PowerShell) code, in terms of version control, backups, portability etc. In this presentation I demonstrated how my PowerShell code is typically broken down into functions and then placed into modules. This allows me to make very generic code for granular tasks, typically either to plug a gap missing from the out-of-the-box cmdlets or maybe stringing a few of them together. As a consultant this enables me to build up a toolkit of functions for particular scenarios gained over various different experiences and use them in a modular fashion where needed for each particular project. However, once these number in the hundreds how do you manage them effectively? I need them to:

  • be easily available depending on where I am working
  • be backed up
  • track changes via version control, useful even if you are not working in a team developing code together – mostly so I can remember how or why I changed something :-)

So I’m going to run your through the system I have found that works for me. It uses the following components:

  • A Dropbox account to sync the code between different machines, be available to download via a web browser and also store the code outside of my home lab. This means I can get access to my functions pretty much wherever I am and whether I am using my own or a customer machine.
  • A Linux VM in my homelab to run Git and Atlassian Stash for version control – $10 for a 10 user license (free to try out for 30 days)
  • Atlassian SourceTree. Free Git client for Mac or Windows

DropboxCode Stash I discovered Stash via a previous customer and found it to be a very useful and easy to use add-on to manage Git repositories. It’s possibly overkill for my current needs, but I have worked on shared code projects in the past and it could be useful to have an easy way to do this in the future. If you are thinking “why not use GitHub?”, there are reasons for this. While I share a lot of my code via this blog and possibly via GitHub in the future, there are some commercial and other reasons why I’m not able to share everything. GitHub has private repositories for this, but they start at $7 a month, so this approach with a home hosted Stash does me fine for now. Setting Up Stash I have a 1 vCPU, 1GB Ubuntu VM, installed with the Minimal Server option. The first thing to do is install Git. The below command will install Git and any dependencies.

sudo apt-get install git-core

Stash01 Check the versions  of Git and Perl. The version of Git should be 1.7.6 or higher. The version of Perl should be 5.8.8 or higher. (Note that out of the box RedHat / Centos currently do not include a version of Git that supports Stash.)


git --version
perl --version

Stash03 Check the version of Java and install if necessary. The version of Java should be 1.6.0 or higher. The below is the easiest way I found to do this.


java -version
sudo apt-get purge openjdk*
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java7-installer
java -version

Stash04 Create an install folder, download the Stash installation files (obviously change the version number for that which you wish to download) and extract the download.


sudo mkdir /var/stash
sudo mkdir /var/stash/install
sudo mkdir /var/stash/home
cd /var/stash/install
sudo wget http://www.atlassian.com/software/stash/downloads/binary/atlassian-stash-2.11.4.tar.gz
sudo tar zxf atlassian-stash-2.11.4.tar.gz

Create a user account to run Stash under.


sudo /usr/sbin/useradd --create-home --home-dir /usr/local/stash --shell /bin/bash stash

Set STASH_HOME in setenv.sh to /var/stash/home.


sudo vi atlassian-stash-2.11.4/bin/setenv.sh

Stash05 Change the ownership of the stash folder and below to the stash account


sudo chown -R stash /var/stash

Start Stash.


/var/stash/install/atlassian-stash-2.11.4/bin/start-stash.sh

Stash06 Navigate to the Setup Wizard at http://hostname:7990 Take the option for an Internal database Stash07 Enter your license key Stash08 Enter Administrator credentials and Go to Stash (I am not using the accompanying Jira product) Stash09 This will take you to the login page where you can authenticate with the credentials you just created. Stash10 You should now see the Stash Welcome Page. Stash11 Connecting the SourceTree Client to Stash Download the SourceTree Client for Mac or Windows and get it installed. Once installed, fire up the client. You may be prompted to install Git for your local client to issue Git commands, go with the embedded version if so: Stash12 Say No to Mercurial (unless you wish to use that as well) Stash13 Enter your information – if you have a GitHub account and wish to use repositories there it can be useful to add the associated email account here. Stash14 Accept the recommendation to use Putty Stash15 If you want to use an SSH key then enter that here, I’m not going to for this tutorial. Stash16 Enter your credentials for the Stash website (and others if you wish to) Stash17 Once complete, the SourceTree client will open. They are pretty regular at providing updates for it, so you may be prompted to install further updates before first use. Stash18 Stash19 Stash20 Now we need to create a project and a repository. First of all through the web interface to Stash create a relevant project. (I liked the way it shortened this one to POW – reminds me of the old Batman series) Stash20a Stash20b Now create a repository Stash20c Stash20d Further down this page you will find instructions on how to upload existing code, which in my case is what I want to do. Stash26a From the SourceTree client open up a Git terminal and run the above commands: Stash26 Stash27 Stash28 At this point if you navigate back to your Stash website you should see your files have been uploaded. You can view inside the code too via the webbrowser. Stash31 Now I need to configure the SourceTree client to be aware of this repository. Click the button for Clone / New and choose Add Working Copy. Enter the path to your working folder and Add. Stash29 It is now available in the SourceTree client. Stash30   So now when I either:

  • Edit code directly on my laptop
  • Or copy code edited elsewhere and pop it in the Dropbox folder

Following synchronisation with the Dropbox folder, the SourceTree client will show that files have changed and require committing. In the below example a couple of minor spelling mistakes have been corrected. SourceTree shows the file has been updated and also what has been changed since the previous version. Red line was removed, Green line was added. Stash32 Right-click the file and choose Commit. Stash33   Enter an explanation for what was changed in this version and it also makes sense to select Push commits immediately to origin so that you create the commit and also submit it in the same action. Stash34 Assuming there were no errors then SourceTree should display no files to commit and navigating to the Log / History tab you will notice that a history of your changes will start to build up. Again, useful if you need to track back and see what changed and when. Stash35

Using PowerShell Aliases in a Module

Creating your own aliases in PowerShell is pretty straightforward. The New-Alias cmdlet allows you to create handy shortcuts for existing cmdlets or functions you have created yourself.

I write a lot of functions for my own modules, so having shortcuts for some of these functions would be pretty useful when using them via a module. However, when I first added some to one of my modules they weren’t available for use. Let’s have a look at an example to see what you need to do to make them available.

We have a module TestModule containing one function and one alias.

ModuleAlias01

The contents of the module are:


function Write-HelloWorld {

Write-Host "Hello World"
}

New-Alias -Name HelloWorld -Value Write-HelloWorld

However, when I import the module the function is available, but the alias is not.


Import-Module TestModule
Write-HelloWorld
HelloWorld

ModuleAlias02

To make the alias available, we need to add the following line to the module file , since by default a module will only export functions.


Export-ModuleMember -Alias * -Function *

ModuleAlias03

Now if we re-import the module (note we need to use the -Force parameter since the module is already loaded) the HelloWorld alias is available.


Import-Module TestModule -Force
HelloWorld

ModuleAlias04

PowerShell Tail Equivalent

Saw this tweet yesterday about an equivalent of tail -f in PowerShell

Tail01

(I think Simon meant Get-Content)

In PowerShell v3 the Get-Content cmdlet received an additional parameter -Tail. This means rather than pulling back the whole of a text or log file, you can just get the last x lines, e.g.


Get-Content .\vmkernel.log -Tail 10

Tail02

Nice, but how can we make this more friendly to those used to typing tail -f?

PowerShell ships with some built-in aliases to help shorten your typing when working at the command line and / or to decrease the initial learning curve. For example cat is an alias for Get-Content, so we could type:


cat .\vmkernel.log -Tail 10

but that just looks a bit weird!

So, since it is possible to create your own aliases with New-Alias, we can combine that with the below Get-ContentTail function to get an experience similar to tail. Stick both in your PowerShell profile and it’ll be available anytime you open a session.


New-Alias tail Get-ContentTail

As Simon also points out above, we can use the -Wait parameter to get a similar experience to -f in Unix. So with my modified version you can do this:


tail .\vmkernel.log 10 -f

Tail03

Now if I simulate (via Wordpad) an application adding lines to that file

Tail04

you’ll see the lines appear in the PowerShell session.

Tail05


function Get-ContentTail {
<#
 .SYNOPSIS
 Get the last x lines of a text file

 .DESCRIPTION
 Get the last x lines of a text file

 .PARAMETER Path
 Path to the text file

.PARAMETER Lines
 Number of lines to retrieve

.INPUTS
 IO.FileInfo
 System.Int

.OUTPUTS
 System.String

.EXAMPLE
 PS> Get-ContentTail -Path c:\server.log -Lines 10

.EXAMPLE
 PS> Get-ContentTail -Path c:\server.log -Lines 10 -Follow

#>
[CmdletBinding()][OutputType('System.String')]

Param
 (

[parameter(Mandatory=$true,Position=0)]
 [ValidateNotNullOrEmpty()]
 [IO.FileInfo]$Path,

 [parameter(Mandatory=$true,Position=1)]
 [ValidateNotNullOrEmpty()]
 [Int]$Lines,

[parameter(Mandatory=$false,Position=2)]
 [Switch]$Follow
 )
 try {

if ($PSBoundParameters.ContainsKey('Follow')){

Get-Content -Path $Path -Tail $Lines -Wait

}
 else {

Get-Content -Path $Path -Tail $Lines
 }

}
 catch [Exception]{

 throw "Unable to get the last x lines of a text file....."
 }
 }

Testing for the Presence of a Registry Key and Value

There are a number of different ways to test for the presence of a registry key and value in PowerShell. Here’s how I like to go about it. We’ll use an example key HKLM:\SOFTWARE\TestSoftware with a single value Version:

RegistryValue

Check for the key

You can use the Test-Path cmdlet to check for the key, but not for specific values within a key. For example


Test-Path 'HKLM:\SOFTWARE\TestSoftware'

but not


Test-Path 'HKLM:\SOFTWARE\TestSoftware\Version'

RegistryValue02

So for the value we need to work a bit harder. Using the below function we can see if Get-ItemProperty contains the value or not.


function Test-RegistryValue {

param (

 [parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]$Path,

[parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]$Value
)

try {

Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop | Out-Null
 return $true
 }

catch {

return $false

}

}

So for our example:


Test-RegistryValue -Path 'HKLM:\SOFTWARE\TestSoftware' -Value 'Version'
Test-RegistryValue -Path 'HKLM:\SOFTWARE\TestSoftware' -Value 'Banana'

RegistryValue03

 

Reporting On Installed Windows Programs Via The Registry

Quite a common request for working with Windows machines is to report the software installed on them. If you don’t have a centralised system for reporting on client software (many places don’t) then you may turn to some form of scripted method to obtain this information.

Most people tend to head to Add / Remove Programs when thinking about what software is installed in Windows. However, not all applications will always populate information in there, depending on how they have been installed. Additionally, to query that information you would typically query the WMI class Win32_Product, however this can lead to performance issues.

A better approach if going down this route is to look in the registry and the key  HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall.

The following function illustrates how to access this data by using this .NET approach to retrieve registry data. Note: this also works with remote computers.

[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('RegistryHive','ComputerName')

We check for the DisplayName on each subkey before adding the result to the output, since for some reason some keys are empty.

$DisplayName = $SubKeyValues.GetValue('DisplayName')<br />if ($DisplayName){........

Here’s the function which you could flesh out further for your own requirements.

Update 03/02/2014: As Peter points out in the notes, if you have 32bit software installed you have an extra place to check when using this method, so you will also need to make the same checks in the following key:

HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall


function Get-InstalledSoftware {
<#
 .SYNOPSIS
 Retrieve installed software from the registry

 .DESCRIPTION
 Retrieve installed software from the registry

 .PARAMETER ComputerName
 Name of the computer to check

.INPUTS
 System.String

.OUTPUTS
 System.Management.Automation.PSObject.

.EXAMPLE
 PS> Get-InstalledSoftware -Computer Server01

 .EXAMPLE
 PS> "Server01","Server02" | Get-InstalledSoftware

#>
[CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

Param
 (

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

)

begin {

 $OutputObject = @()
 }

 process {

try {
 foreach ($Computer in $ComputerName){

 $Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Localmachine',$Computer)
 $UninstallTopKey = $Registry.OpensubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall",$false)
 $UninstallSubKeys = $UninstallTopKey.GetSubKeyNames()

ForEach ($SubKey in $UninstallSubKeys){

 $Path = "Software\Microsoft\Windows\CurrentVersion\Uninstall\$SubKey"
 $SubKeyValues = $Registry.OpensubKey($Path,$false)

$DisplayName = $SubKeyValues.GetValue('DisplayName')

if ($DisplayName){

 $Object = [pscustomobject]@{

 ComputerName = $Computer
 DisplayName = $DisplayName
 DisplayVersion = $SubKeyValues.GetValue('DisplayVersion')
 UninstallString = $SubKeyValues.GetValue('UninstallString')
 Publisher = $SubKeyValues.GetValue('Publisher')
 InstallDate = $SubKeyValues.GetValue('InstallDate')

}
 }

 $OutputObject += $Object

}
 }
 }
 catch [Exception]{

 throw "Unable to get registry data....."
 }
 }
 end {
 Write-Output $OutputObject
 }
}