Tuesday, 2 April 2019

Refactoring PSM1 based modules into seperate ps1 files

A lot of my old powershell modules have grown to be quite big and unweildly.

At work we have now standardised on using PSAKE to create them and use a structure that has a public and private set of folders that contain ps1 files that make up the modules functions. These are automatically dot sourced when the module loads.

I'm not going to claim credit for doing it that way, its based on some complex googling, or maybe just plain luck.

In an effort to refactor and make some of these more readable, supportable, easier to test using pester etc etc.. I've knocked up a quick and dirty script for 'refactoring' the module into seperate files.

It's not perfect but it was spend a little time writing a script to do it, rather than spending the time manually in VSCode or Notepad++

The code uses powershell's Abstract Syntaxt Tree / Language Parser. it might help someone, it might not :)

Code is here: https://gist.github.com/davidwallis3101/58de8462bf37c8683b377cb0c7a5a95b

Thursday, 22 June 2017

Creating a pester script with powershell

I’m knocking up a few more infrastructure validation tests and couldn’t be bothered to write everything, so with the quick script it gets you a starting point, creating a simple test for each value that ends with ver (version).

Obviously this needs further work, but not a bad starting point.

$results = Get-ItemProperty -Path "HKLM:\Software\Wow6432Node\TrendMicro\PC-cillinNTCorp\CurrentVersion\Misc."

foreach ($property in $results.PSObject.Properties) {
    if ($property.Name -like '*Ver') {
        write-host "It `"$($property.Name) has a valid version`" {"
        Write-Host "    `$results.$($property.Name) | Should Be `"$($property.Value)`""
        Write-Host "}"

Friday, 18 November 2016

Getting nested runbooks in system center orchestrator

I have recently written a function for checking if a runbook is checked out (will post that shortly) but I wanted a way of checking if any child runbooks were checked out too..

I did a google and found a link on ServerFault for a sql query that did just that (I've lost the link), I've taken that SQL query and wrapped it in a Powershell step so that I can use it easily on my desktop inside or outside of orchestrator - Outside of orchestrator will be to be used in my pester tests which I use to verify runbooks, but also the whole end to end process, IE are all required runbooks checked in?

The script is as follows:

function Get-ChildRunbooks {


   Gets the runbooks that are called by a parent runbook, you will need permissions to query the sql database
            Requires PS V3

  .PARAMETER  RunbookName
   The runbook name to check

  .PARAMETER  Database
   The database name to connect to

  .PARAMETER  Server
   The name of the server hosting orchestrators SQL instance
  .PARAMETER  Recurse
   Switch for enabling recursion
   PS C:\> Get-ChildRunbooks -Name 'VMWare'

   PS C:\> Get-ChildRunbooks -Name 'VMWare' -Recurse
   PS C:\> Get-ChildRunbooks -Name 'VMWare' -Database Orchestrator2012

   PS C:\> Get-ChildRunbooks -Name 'VMWare' -Database Orchestrator2012 -Server Orchestrator001


  [Parameter(Position=0, Mandatory=$true)]

  $Database = 'Orchestrator',
  $Server = 'myserver',
 try {
  $Results = @()
$Query = @"
 SELECT POLICIES.Name AS [SourceRunbook],
   RIGHT(TRIGGER_POLICY.PolicyPath, CHARINDEX('\', REVERSE(TRIGGER_POLICY.PolicyPath))-1) as ShortPolicyPath
 WHERE (OBJECTS.Deleted = 0) AND (OBJECTS.ObjectType = '9C1BF9B4-515A-4FD2-A753-87D235D8BA1F') AND (POLICIES.Name = '$RunbookName')
        $connectionString = "Server=$Server; Database=$Database;Trusted_Connection=Yes; Integrated Security=SSPI;"
        #connect to database
        $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)

        $command = $connection.CreateCommand()
        $command.CommandText = $Query
        $command.CommandTimeout = 30

        $adapter = New-Object System.Data.SqlClient.SqlDataAdapter $command
        $dataset = New-Object System.Data.DataSet
        $adapter.Fill($dataset) | out-null

        foreach ($Row in $dataset.Tables[0].Rows)
                SourceRunbook= $Row.SourceRunbook;
            if ($Recurse) {
                $results+= Get-ChildRunbooks -Name $Row.ShortPolicyPath -Recurse
        return $results
 catch {

Thursday, 23 July 2015

Getting all automation connections from within SMA of a given type

I'm currently looking at how I can migrate some runbooks from System Center Orchestrator 2008 R2 to Service Management Automation (SMA). One issue I have in Orchestrator is that it isn't possible to subscribe to a configuration activity if you package it up into an Orchestrator Integration pack (OIP). To get around this, you can use the invoke.net activity and call the Class directly from the DLL.

Why does it matter your thinking?  One scenario we have is that we want to be able to have a common runbook, and execute it against multiple load balancers. Each of these connections is then stored in Orchestrator.. and the caller passes the configuration name in (which is actually the lb hostname) and then it connects with the correct credentials.

Anyway I was thinking about how I could do the same or achieve the same in SMA, and realised that it was a non issue, I then started migrating some other runbooks, and one task requires running against all instances of an env.. for example VMWare - Go get all VM's across all vcenters..

I thought I'll just get all the connections of a given type and easy done, however Get-AutomationConnection has a mandatory parameter of the name.. and wildcards don't work.

I had a quick nosy in the database and realised it should be easy to do what I needed, so what I have done is create a workflow that queries the relevant table in the DB for the connections and then returns the name so you can use that to return the credential / connection pair required. This maybe achievable in other ways but I am new to SMA :)

The scripts are available here: Get-AutomationConnections.ps1 and an example of calling it here: UseMultipleConnections.ps1

Wednesday, 8 July 2015

Executing SMA runbooks from linux

I've been trying to work out a way of executing SMA runbooks from some of our linux systems.

so I have knocked together a crude python script that can be further extended for doing such a task..

python isn't my language of choice - this is probably quite apparent in the lack of classes etc.

typically SMA uses ODATA for the interface via REST - this was proving too much of an issue, so I resorted to regex and extracting the data from the XML that I needed.. Initially I was going to use XPATH to do it, but things like elementtree didn't seem to like the 'XML'.

Below is a pic of the output:

Anyway I have uploaded the script to my codeplex site located here: http://smaworkflows.codeplex.com/SourceControl/latest#Python/SMARunbooks.py

Also a thanks should be mentioned to Laurie Rhodes for the information provided here:

Saturday, 4 July 2015

SMA Workflows

I'm trying to get to grips with SMA and what it will take for us to migrate existing integration packs and runbooks that we have authored in C#

I'm going to try and keep everything we have done open, So I am uploading all the scripts to a new codeplex site I created located here: smaworkflows.codeplex.com

I've just created one for sending text messages via a provider called AQL for more details see here: www.aql.com/sms/

The above script is located here: http://smaworkflows.codeplex.com/SourceControl/latest#Misc/Send-SMS.ps1 if anyone is interested.

Wednesday, 20 May 2015

Creating DHCP option 121 or 249 string via powershell

We have a number of networks that addresses were statically assigned.. we are now moving these to DHCP to aid with the DevOps principles and destroying machines and rebuilding from code. In order to facilitate this I needed to deliver the static routes via DHCP.. It turns out its a bit of a pain in the arse to do this.. From a stack overflow post:
You're seeing these because option 249 must be specified as as BINARY value, not as an IPADDRESS value. If you can't set this with the GUI then you'll have to convert your desired route into a hexadecimal string yourself. An example would be as follows: accessible via converts into "180a01010a010101". The first octet, "18", is the number of bits of subnet mask (0x18 = 24 decimal). The next octets are the network ID (0a = 10, 01 = 1, 01 = 1, for "10.1.1"), padded by zeros on the right if the subnet mask doesn't end on an even octet boundary. The last four octets are the IP address of the gateway. Set the value in the GUI and you'll be happier.
Well I had too many to do, so I couldn't do it with the GUI really and it was too prone to error with fat fingers.. so I knocked up a quick script to accomplish this.. a word of warning is that I have only done /20 /23 and /24 networks with this.. so test it and verify in the GUI!!

 Here's the powershell script with an example at the end of how its run for multiple routes...
function ConvertTo-MaskLength {
      Returns the length of a subnet mask.
      ConvertTo-MaskLength accepts any IPv4 address as input, however the output value 
      only makes sense when using a subnet mask.
    .Parameter SubnetMask
      A subnet mask to convert into length
 Function pinched from http://www.indented.co.uk/2010/01/23/powershell-subnet-math/

    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]

  process {
    $Bits = "$( $SubnetMask.GetAddressBytes() | ForEach-Object { [Convert]::ToString($_, 2) } )" -replace '[\s0]'
    return $Bits.Length

function GenerateHex {
      Returns the hex value for creating a static route (DHCP option 121 / 249) - RFC3442
      GenerateHex creates the hex value for creating a static route.. untested with anything other than /23 /24
    .Parameter SubnetMask
     EG or /24
    .Parameter NetworkAddress
      The Network address IE
    .Parameter Gateway Address
      The IP Address of the gateway to send the traffic to IE    

     [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
  [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $True)]
  [Parameter(Mandatory = $True, Position = 2, ValueFromPipeline = $True)]

 # Convert subnet mask to correct format
 if ($SubnetMask -like "/*") {
  $SubnetMask = $SubnetMask.Replace("/","")
 }elseif ($SubnetMask -like "*.*") {
  $SubnetMask = ConvertTo-MaskLength -SubnetMask $SubnetMask

 # Split network into an object by octet
 $NetAddress = $NetworkAddress.split('.')

 # Step through the correct number of significant octets (mask / 8 then rounded up) and convert to hex
 foreach($i in 1..([math]::Ceiling($SubnetMask / 8))){
  $Network += "{0:X2}" -f [convert]::ToInt32($NetAddress[$i-1])

 # Convert Gateway Address to Hex
 Foreach ($octet in $GatewayAddress.split('.')) {
  $Gateway += "{0:X2}" -f [convert]::ToInt32($octet)

 # Convert Mask to Hex
 $maskHex = "{0:X2}" -f [convert]::ToInt32($SubnetMask)

 # return calculated output
 $value =  $maskHex + $Network + $Gateway
 return $value

$String = ""
$String += GenerateHex -NetworkAddress -SubnetMask -GatewayAddress
$String += GenerateHex -NetworkAddress -SubnetMask -GatewayAddress

# Display command rather than execute it
Write-Output "netsh dhcp server scope set optionvalue 121 binary $($string)"
I then needed to convert some existing boxes from having lots of local persistent routes and was planning on using something like this as the basis of starting a script..
Get-WmiObject Win32_IP4PersistedRouteTable | Select-Object Destination, Mask, Nexthop
however I quickly discovered the machine I was looking at doesn't have powershell installed.. Given I'm in a rush I resorted to VBS as it was my scripting language of choice prior to powershell.. so the following script was used to export the routes and generate the netsh command.. Crude I know, but not as crude as multiple static routes on a server, rather than using a Router!
' Export Routes.vbs
On Error Resume Next
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_IP4PersistedRouteTable",,48)

For Each objItem in colItems   
    wscript.echo "$String += GenerateHex -NetworkAddress " & objItem.Destination & " -SubnetMask " & objItem.Mask & " -GatewayAddress " & objItem.NextHop