Category Archives: Administration

Powershell – ForEach-Parallel

Background

In PowerShell I often find the need to speed things up when having to do querying lots of similar things – usually getting the same stuff from multiple machines. So I ventured out onto the Interwebs and over time slap together a PowerShell module that does parallel processing. I’m not claiming this to be purely my work – I only added and refined it a bit for my needs.

This example works with ‘older’ versions of PowerShell (think version 4/5 and perhaps 3). The latest versions of PowerShell (afaik version 7) now has something like this built in.

Implementation

Using any sort of parallel coding requires a different approach than normal procedural (Start at the top and run code line by line until you get to the end). Basically it is multi-threading but PowerShell ‘out of the box’ has nothing like it. To implement multi threading you need to use ‘runspaces’ which effectively means you are running multiple instances of PowerShell.

The ForEach-Parallel function takes a scriptblock as input. Optionally you can also specify the number of threads used to do the parallel processing.

#ForEach-Parallel.psm1
function ForEach-Parallel {
     param(
         [Parameter(Mandatory=$true,position=0)]
         [System.Management.Automation.ScriptBlock] $ScriptBlock,
         [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
         [PSObject]$InputObject,
         [Parameter(Mandatory=$false)]
         [int]$MaxThreads=5
     )
     BEGIN {
         $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
         $pool = [Runspacefactory]::CreateRunspacePool(1, $maxthreads, $iss, $host)
         $pool.open()
         $threads = @()
         $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($_)r`n" + $Scriptblock.ToString())
     }
     PROCESS {
         $powershell = [powershell]::Create().addscript($scriptblock).addargument($InputObject)
         $powershell.runspacepool=$pool
         $threads+= @{
             instance = $powershell
             handle = $powershell.begininvoke()
         }
     }
     END {
         $notdone = $true
         while ($notdone) {
             $notdone = $false
             for ($i=0; $i -lt $threads.count; $i++) {
                 $thread = $threads[$i]
                 if ($thread) {
                     if ($thread.handle.iscompleted) {
                         $thread.instance.endinvoke($thread.handle)
                         $thread.instance.dispose()
                         $threads[$i] = $null
                     }
                     else {
                         $notdone = $true
                     }
                 }
             }
         }
     }
 }
 export-modulemember -function ForEach-Parallel 

To make use of this ‘module’ save it to a file named ForEach-Parallel.psm1 and place it in C:\Users\{your user account}\Documents\WindowsPowerShell\modules\ForEach-Parallel

Example

#Example
$computers = 'computer1','computer2','computer3'
 $computers | ForEach-Parallel -MaxThreads 20 -ScriptBlock {
     $computerToPing = $_
     [int]$ResponseTime = 0
     [string]$ResolvedName = ''
     $StepPing = (Test-Connection -ComputerName $computerToPing -Count 1 -ErrorAction SilentlyContinue)
   if ($StepPing -ne $null){
      $ResponseTime = $StepPing.ResponseTime 
   } 
   New-Object psobject -Property @{     
    Name               = $computerToPing     
    ResponseTime       = $ResponseTime                  
   }
 }

Summary

There are a few things to watch out for – like any multi-threading coding. While the code is running there is no indication how far each ‘thread’ is – you have to wait until all of them are done. Threads are independent so they cannot reference each other or even variables in the calling thread.

So.. happy multi-threading.

SSH on Windows 10

I remember that they (Microsoft) mentioned the bash shell on Windows 10 a while ago and other Linux/Ubuntu integration stuff but at the time it seemed to be just a gimmick.

But since I’m looking into Nagios and some Windows monitoring I now the need to daily connect to my Nagios server making configuration changes. So far I’ve been using good old Putty/WinSCP and it works well. Then I started looking at a way to generate configurations files for some Windows machines and upload them automatically via PowerShell (or it could end up being another tool created in C# eventually). This is how I stumble across the Windows 10 built in SSH functionality – it’s been there for at least a year! (since build 1803).

That means you can simply use the ssh command in a Windows command shell (or PowerShell) and send commands to your Linux server. To start it simple open a command prompt (cmd.exe) and run ssh:

As can be seen it supports all the basics of any ssh client.

So that cover the basics. The real power comes when you start mixing it with PowerShell and proper scripting and/or batch files.

One very interesting (and incredibly useful) example is something Scott Hanselman posted – how to automatically log into a remote linux machine – see this.

QuickMon 5 Beta

This is just a quick note to say that QuickMon 5 has reached Beta status. It is now mostly done with only some minor bug fixes and tweaks that needs left to be done.

Most of the functionality of version 4 was copied over – with a few exceptions. More on that at a later stage. The biggest improvements are around the application GUI to make it easier to see details without drilling down too much.

Download a copy from Github – https://github.com/RudolfHenning/QuickMon/releases

Feedback welcome.

SSH from PowerShell

I’ve been playing with more non Microsoft technologies recently and even have a few Linux VMs running here and there. In the process I’ve learned a lot about interacting between the two worlds/environments using technologies like SSH and even running the Powershell Alpha previews on my Linux VMs. Then I got ‘bored’ and looked for a way to programmatically interact with my VMs (for monitoring) and discovered the open source project SSH.Net which I now have incorporated into even QuickMon. Then I got even more bored and started looking for a way to use SSH from Powershell and came across the SSH-Sessions module which incidentally also use SSH.Net to give Powershell a way to call/connect using SSH.

No PassPhrase

This all works nicely except… SSH-Sessions had one shortcoming.. which I just had to fix. The creator(s) of this module implements a way to specify a key file when connecting to an SSH server but does not provide a way to specify the ‘PassPhrase’ at all! WHY?

Any way, I knew the SSH.Net library does support it since I actually make use of this functionality in QuickMon. Then I started digging inside the SSH-Session script files and made a few adjustments…

Fix

To enable PassPhrase functionality I simply had to add the following code in the ‘New-SshSession’ function:

Change the function header to:

function New-SshSession {
    param([Parameter(Mandatory=$true)][string[]] $ComputerName,
          [Parameter(Mandatory=$true)][string]   $Username,
          [string] $KeyFile = '',
          [string] $PassPhrase = 'blankPassphrase',
          [string] $Password = 'SvendsenTechDefault', # I guess allowing for a blank password is "wise"...          
          [int] $Port = 22,
          [switch] $Quiet
    )

and then inside the function:

if ($KeyFile -ne '') {
        if (-not $Quiet) {
            "Key file specified. Will override password. Trying to read key file..."
        }
        if ($PassPhrase -eq 'blankPassphrase') {
            $SecurePassPhrase = Read-Host -AsSecureString "key provided. Please enter pass phrase for $KeyFile"
            $PassPhrase = ConvertFrom-SecureToPlain $SecurePassPhrase
        }
        if (Test-Path -PathType Leaf -Path $Keyfile) {
            $Key = New-Object Renci.SshNet.PrivateKeyFile( $Keyfile, $PassPhrase ) -ErrorAction Stop
        }
        else {
            "Specified keyfile does not exist: '$KeyFile'."
            return
        }   
    }

Example

And with the you can now use the module to connect to SSH using a Key file that has a PassPhrase. If you don’t specify the PassPhrase the script will prompt you for one. If you actually don’t have a PassPhrase at all then pass an empty string as the value of PassPhrase (not tested but it should work).

Import-Module SSH-Sessions
New-SshSession -Computer mySSHServer -Username me  -KeyFile 'c:\mykeys\mykey.key' -PassPhrase 'somePhrase'
Invoke-SshCommand  -ComputerName mySSHServer -Command "cat /proc/cpuinfo | grep processor" -Quiet
Remove-SshSession -RemoveAll -Quiet

SCOM agent Info script

When you have a case where you start integrating your in house SCOM 2012 R2 environment with VMs that are hosted in Azure and ‘other’ people keep on screwing up your SCOM agents with the OMS version you need a way to track what version is actually installed on a machine – plus whether the original SCOM 2012 R2 config is still in tact – which is  not the case since the upgrade process from SCOM 2012 R2 agent to the OMS version totally screws up the SCOM 2012 R2 config (like in F*ing deletes it !!!)

Therefore I had to create a little Powershell script to help check suspected machines if they have been affected by this issue. From the SCOM console side it just appears the Agent is ‘dead’ even though if you check manually it is still running (but of course the config is gone).

if ($args.count -ne 0) {
    $ComputerName = $args[0]    
}
else {
    $ComputerName = Read-Host -Prompt 'Computer Name'
}

$pingTest = (Test-Connection $ComputerName -Count 1 -TTL 255 -ErrorAction SilentlyContinue) 
if ($pingTest -ne $null -and $pingTest.IPV4Address -ne $null){
    write-host "IP Address : " $pingTest.IPV4Address.IPAddressToString -foregroundcolor "green"
    $serviceDetails = Get-Service -ComputerName $ComputerName | where DisplayName -Like 'Microsoft Monitoring Agent'
    if ($serviceDetails -ne $null ) {
        write-host "Health Service state: " $serviceDetails.Status.ToString() -foregroundcolor "green"

        $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName)
        $AgentGroupKeys = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\HealthService\Parameters\Management Groups")
        write-host "Groups: " $AgentGroupKeys.SubKeyCount.ToString() -foregroundcolor "green"
        $AgentGroupKeys.GetSubKeyNames() | % {
            if ($_ -like "AOI*") {
                write-host " OMS" -foregroundcolor "yellow"
            } else {
                write-host " $_" -foregroundcolor "green"
            }            
        }        
        
        $VersionNo = "NOT found!"
        $RegKeyProducts = $Reg.OpenSubKey("SOFTWARE\Classes\Installer\Products")
        $RegKeyProducts.GetSubKeyNames() | % {
            $RegKeyProduct = $Reg.OpenSubKey("SOFTWARE\Classes\Installer\Products\" + $_)
            if ($RegKeyProduct.GetValue('ProductName') -eq "Microsoft Monitoring Agent") {
                $VersionNo = $RegKeyProduct.GetValue('Version').ToString("X")
            }
        }

        if ($VersionNo -ne "NOT found!") {
            write-host "Agent version" $VersionNo -foregroundcolor "green"
        }
        else {
            write-host "Agent version not found!" -foregroundcolor "red"
        }

    }
    else {
        write-host "Health service NOT found!" -foregroundcolor "red"
    }
}
else {
    write-host "$ComputerName is not pingable!" -foregroundcolor "red"
}

This script first checks whether the machine (VM) is pingable, if it is then whether the HealthService service is installed, if so what state it is in and what version it is. It also lists the management groups the agent reports to including OMS.

Have fun kidz…

The case of the missing ‘Process’ Performance counter

I recently had the need to do some monitoring of the Explorer.exe process (like one of the Windows 10 Insider Preview bugs) that every so often went bananas (Minions would like it…) for no reason at all. This is a pain if you have other processes on the same machine that is sensitive to things like running in ‘low’ priority (Like Boinc workloads). This effectively block other processes to run or just plain make the whole machine feel slow and sluggish.

Then I discovered the problem that perfmon could not ‘see’ the ‘Process’ category. This is weird since I  thought this would be one of the most basic performance counters that Windows always had and probably still have. Somehow the ‘Process’ category disappeared or became corrupted or something – I thought.

Anyway, after some digging around (aka Google) I found an old article about similar issues people had with older versions of Windows (even server versions). The culprit (at least in this case) was that for some reason the registry key to enable the ‘Process’ category was disabled. I have no idea how that happen (no I deny any involvement whatsoever… 🙂 ).

To fix do this: (standard registry editing disclaimer: do it at your own risk).

  1. Open Regedit
  2. Find the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PerfProc\Performance
  3. Check the value of ‘Disable Performance Counters’. If it is anything but 0 it means it is disabled.
  4. Change the value to 0 and save.
  5. Restart whatever performance counter monitoring tool you are using since it will not be aware of the change until that process restart.
  6. Jump up and down for joy! (just because you can).

 

QuickMon 4

Just a quick post to announce the first (official) release of QuickMon 4.

There might be some minor bugs that must be ironed out but the basic product is done.

Have fun.

QuickMon 4 Beta

Heads up folks! A public Beta of QuickMon 4 has been released. Please go to QuickMon to download and play with it.

People that has not seen the Alpha yet would notice some major changes compared to version 3.x. The biggest change it that a Collector (now called Collector host) can contain multiple agents of multiple types all tested together to raise one single alert if needed. Of course you can still use it the old way with a collector host containing only a single agent type and have child collector hosts depending on it.

Another area of big change is that viewing the details of a collector’s state, stats, history, alert details and other info gathered are all placed inside a single view (window). This makes it a lot easier to see what is happening with alerts. This detail view is now also ‘Remote host’ aware so it will run and gather details from a (QuickMon 4) remote host as if it is running on ‘that’ machine.

Ok, go download it and have fun… and report back if you have issues or suggestions.

QuickMon HTTP Activation catch

I recently created a test Windows 8 (8.1) VM to just play around and took the opportunity to test the QuickMon install as well. Usually I simply test on my own machine where I know it works already. This is of course not a major problem in itself but it means I’m not testing what will happen to the tool if you install it on a fresh/clean machine.

Surprisingly/luckily just about everything worked first time – not that I expected less (ye ye a bit arrogant I know 😉 ) and I was happy that QuickMon ‘out of the box’ is so good. However…. there is one thing that did not work as expected (ok ok it did not work at all) – The Remote host functionality built into the Windows Service.

This is actually not a QuickMon problem per se – it is a requirement of or dependency on the underlying operating system that the WCF functionality must be available. The remote host functionality of QuickMon uses HTTP Activation which is not installed by default on a new Windows 8 (or probably previous versions) installation.

The ‘fix’ is really simple – simply go to ‘Control Panel’ -> ‘Turn Windows features on or off’ -> .Net Framework 4.5 Advanced Services (or 4.0 if present) -> WCF Services -> check HTTP Activation on. Then do a reboot even though Windows doesn’t ask for it after the install – somehow the functionality does not really work right away. It should then be working provided you checked all the other know issues – like firewalls and stuff.

I’ll see if I can build in some check into the service itself to generate an error (Event log or something) to high-light if this feature is not available on the operating system where it needs to run.

One more thing to add to QuickMon… hehe

How to access the ‘Service Controller Database’ remotely on a Workgoup network

After having some issues with my home computers not being able to ‘see’ each others services I started digging around for solutions – having already checked all the other possible problems. The main issue is that all these computers are running on an old fashioned ‘Workgroup’ since I don’t have an Active Directory Controller (no Windows Servers). Thus all the machines connect using the same username/password combination.

I’ve made sure of things like firewalls, user accounts (as mentioned already), UAC etc. are all ok but still I got the ‘Cannot open Service Control Manager Database’ error (Access denied (5)). I started suspecting there must be another level of security that is blocking access to view/start/stop Windows services across different machines. Then I stumbled across articles describing ACLs and the issue that to this day Microsoft has not exposed Service ACL’s through .Net yet (yet they have things like File and Registry ACLs exposed through System.Security namespace…).

The only way to get to these ACL’s are through old fashioned Win32 APIs or using the SC.exe utility. I’ve read that someone wrote a C# wrapper class for these somewhere but I could not find any remaining source of this through Google. Bummer… Then using the SC.exe utility through some nasty loosely coupled integration is the only solution…

To view the current DACL (Discretionary Access Control List) for ‘Service Controll Manager’ you can use the followig command on the source/host computer you want to connect to:

sc sdshow scmanager

That should give you something like this:

D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)

Now, the part that was relevant to me using a ‘Workgroup’ network is the (…AU) part because for some reason even though I’m using the same user account/Password on all machines – and this account is an Administrator on each machine, the system only recognize the user as part of the Authenticated users group ONLY. Thus the  (A;;CC;;;AU) part is not sufficient to allow access to the service control manager database…

To get access to the Service Controller Database you need permissions like this: (A;;CCLCRPRC;;;AU)

To understand what all those letters use please refer to ‘Best practices and guidance for writers of service discretionary access control lists‘ which explains the whole lot. The following command can be used to ‘fix’ the access to the ‘Service Controller Database’: (note that is is just an example!! Check the output of the previous command first)

 sc.exe sdset scmanager D:(A;;CCLCRPRC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)

After running this you should be able to access the  ‘Service Controller Database’ remotely (assuming all the other things have been checked). All good and wonderful! uhmm… Then I discover this does not display all services… bugger again..

Actually there are two parts to the original problem – as I discovered that each Windows service on its own has an ACL that can/should be set. Some ‘system’ services already come with permissions for AU (Authenticated Users) so they are visible by default. Most other and particularly my custom created services don’t have the right ACLs set. To fix that is simple… Just repeat the process above for that particular service – like this (for my QuickMon 3 Service):

Sc sdshow "Quickmon 3 service"

This should give an output like this:

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

As you can see it does not have any permissions for AU. To fix access to that server you simply have to add the AU permissions like this:

sc.exe sdset "Quickmon 3 service" D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

Now of course you have to repeat this for each Windows Service you need access to…

To help in this process I created the following PowerShell script that list all Windows Servers plus their ACLs:

$services = @{}
Get-Service | foreach {
    $DACL = sc.exe sdshow $_.ServiceName
    $service = @{
        'ServiceName' = $_.ServiceName
        'DisplayName' = $_.DisplayName
        'ServiceType' = [string]$_.ServiceType
        'DependsOn'   = [string]$_.ServicesDependedOn
        'State'       = [string]$_.Status
        'DACL' = ([string]$DACL).Trim()
    }
    $serviceObj = New-Object -TypeName PSObject -Property $service
    $services.Add($_.ServiceName,$serviceObj)
}
$services.Values | select ServiceName, DisplayName, ServiceType, State, DependsOn, DACL | Sort-Object ServiceName

Now you have to repeat the whole process for each service… And that is how it’s done… Enough to keep you out of mischief or perhaps enough to get you into it again…