Tag Archives: powershell

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

How to install Powershell on Ubuntu 16.10

I recently tried installing the latest Alpha of Powershell (Alpha 12) on the newly released Ubuntu 16.10 and discovered that the install failed because of a dependency on the library libicu55 which has been replaced in the new version of Ubuntu (libicu57). Actually, the whole .Net Core install fails because of this.

Fortunately this is easy to fix by manually installing the old library from http://cz.archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu55_55.1-7_amd64.deb

Then you can download the latest Powershell version (Alpha 12 as of this writing) from https://github.com/PowerShell/PowerShell/releases

Hopefully the creators of the Powershell packages will release an updated version that is natively compatible with Ubuntu16.10.

 

PowerShell ISE on Windows 10 (10049 preview)

Just a quick tip how to get to the PowerShell ISE in Windows 10 which they (MS) seems to be trying to hide away… 🙂

Launch the normal PowerShell command window – “All Apps – Windows System – Windows PowerShell”. Once it is running pin it to the taskbar. Now if you right click on the pinned icon it will include a menu option under Tasks for ‘Run ISE as Administrator’ and ‘Windows PowerShell ISE’.

You can then launch the ISE and pin that to the taskbar as well if you like…

 

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…

Getting GDI object count per process in Powershell

Ever wanted to query the GDI object count in PowerShell per process?

Here’s how:

"Number of GUI handles per process"
$sig = @'
[DllImport("User32.dll")]
public static extern int GetGuiResources(IntPtr hProcess, int uiFlags);
'@

Add-Type -MemberDefinition $sig -name NativeMethods -namespace Win32

$processes = [System.Diagnostics.Process]::GetProcesses()
[int]$gdiHandleCount = 0
ForEach ($p in $processes)
{
    try{
        $gdiHandles = [Win32.NativeMethods]::GetGuiResources($p.Handle, 0)
        $gdiHandleCount += $gdiHandles
        $p.Name + " : " + $gdiHandles.ToString()   
    }
    catch {
        #"Error accessing " + $p.Name
    }
}
"Total number of GDI handles " + $gdiHandleCount.ToString()

Alternatively you can use Process Explorer from Microsoft (originally sys-internals) if it is installed.

Start or stop BizTalk Applications using PowerShell

This is not actually something new but for anyone else that wants to quickly have a reference to it, here are two PowerShell scripts to stop and start BizTalk applications.

Stopping a specified Application:

if ($args.count -eq 0) {
  "You must specify the BizTalk Application name to stop!"
}
else{
    $BTSAppName = $args[0]
    $SQLInstance = "."
    $BizTalkManagementDb ="BizTalkmgmtdb"
    [void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
    $Catalog = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
    $Catalog.ConnectionString = "SERVER=$SQLInstance;DATABASE=$BizTalkManagementDb;Integrated Security=SSPI"

    $BTSApp = $Catalog.Applications[$BTSAppName]
    if ($BTSApp.Status -ne "stopped")
    {
        "Stopping" + $BTSApp.Name
        $BTSApp.Stop("StopAll")
        $catalog.SaveChanges()
    }
    else
    {
        $BTSApp.Name + " is already stopped"
    }
}

Starting a specified Application:

</pre>
<pre>if ($args.count -eq 0) {
  "You must specify the BizTalk Application name to start!"
}
else{
    $BTSAppName = $args[0]
    $SQLInstance = "."
    $BizTalkManagementDb ="BizTalkmgmtdb"
    [void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
    $Catalog = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
    $Catalog.ConnectionString = "SERVER=$SQLInstance;DATABASE=$BizTalkManagementDb;Integrated Security=SSPI"

    $BTSApp = $Catalog.Applications[$BTSAppName]
    if ($BTSApp.Status -ne "started")
    {
        "Starting " + $BTSApp.Name
        $BTSApp.Start("StartAll")
        $catalog.SaveChanges()
    }
    else
    {
        $BTSApp.Name + " is already started"
    }
}

List all .Net versions using PowerShell

A quick tip (script) how to list all the .Net (major) versions of a list of machines.

$serverListFile = "<Path to file>\computers.txt";
$computers = Get-Content -path $serverListFile

$BASEDOTNETPATH = "\c$\Windows\Microsoft.NET\Framework\"

function CheckNet45($computerName){
  $NETROOTKEY = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4"
  $keyname = $NETROOTKEY + "\\Full"
  try{
    $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $computerName)
    if ($reg -ne $null){
      if ($reg.OpenSubKey($keyname).GetValue("Version").ToString().StartsWith("4.5")){
        return $True
      }
    }
  }
  catch {
    #do nothing
  }
  return $False
}

foreach($computer in $computers)
{
  $computer
  $ok = Test-Connection $computer -Count 1 -Quiet
  if ($ok) {
    $computerBasePath = "\\" + $computer + $BASEDOTNETPATH
    $versionStr = ""
    if (Test-Path ($computerBasePath + "v1.0.3705")){
      $versionStr = $versionStr + "1.0,"
    }
    if (Test-Path ($computerBasePath + "v1.1.4322")){
      $versionStr = $versionStr + "1.1,"
    }
    if (Test-Path ($computerBasePath + "v2.0.50727")){
      $versionStr = $versionStr + "2.0,"
    }
    if (Test-Path ($computerBasePath + "v3.0")){
      $versionStr = $versionStr + "3.0,"
    }
    if (Test-Path ($computerBasePath + "v3.5")){
      $versionStr = $versionStr + "3.5,"
    }
    if (Test-Path ($computerBasePath + "v4.0.30319")){
      $versionStr = $versionStr + "4.0,"

      if (CheckNet45($computer)){
        $versionStr = $versionStr + "4.5,"
      }
    }
    $versionStr = $versionStr.TrimEnd(',')
    "  Installed version(s): " + $versionStr

  }
  else{
   " Is not pingable!"
  }
}

All you have to do is create a text file with a list of machine names (one name per line). It does assume you have Admin rights so you can access the C$ share and read the remote registries.

A little (versioning) pain with Powershell

First thing to mention is I’m not complaining about PowerShell itself or functionality provided with PowerShell. Rather, this is a problem with referencing it from .Net (C#) and versioning when used on another machine where the .Net App was deployed and happen to have an older version of PowerShell (PS for short).

Scenario

If you want to run PowerShell scripts from a C# program like I’m doing in QuickMon now, you have to add a reference to “System.Management.Automation.dll“. I happen to have Version 4.0 of PowerShell on the computer where the C# app is compiled. Now, this is all fun and games until you want to run the same app on another machine that only have, say Version 2.0 of PowerShell. PS 2 does have a version of System.Management.Automation.dll but somehow it is not compatible (at run time). When I compiled the same app on a computer that only has V2.0 of PowerShell and deploy that app to machines that has either PS v2.0 or v4.0 it works on all of them.

If this was C++ it would have made sense (dll versioning very strict) but this is a C# app that supposively should not care about the version of the referenced assembly (and yes, this is a .Net assembly). The methods (I) used in the dll are all the same of both versions.

I even tried generating the System.Management.Automation.dll file manually on the ‘target’ machine and copy that dll to the App’s directory (using the PS “Copy ([PSObject].Assembly.Location) C:\” command) but this gave other versioning referencing errors.

Possible solution

The solution that seems to work for now is to set the ‘Specific Version‘ property on the referenced dll in the C# project to true (making sure it points to the PS 2.0 related version – dll version 6.1.7601.17514). Interestingly enough when switching the project to ‘release’ configuration it display pointing to ‘C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll’. That tells me the PS version ‘2.0’ of that assembly is still available (on my PS 4.0 computer). As a safety precaution I also set the ‘Copy Local’ property on this referenced dll to True to have it deployed with the app.

Deploying this combination seems to work on all ‘target’ machines – I even saw it working on a computer that only has PS 1.0!

All fun and joy… as long as there’s no lawyer sitting on a loo in the bush… with an angry T-Rex nearby…

List all servers where I have an RDP session on (even ones where I’m disconnected)

This is something more than just one administrator has had trouble with. How do you find out what servers/machines you have worked on at some point and happened to simply use the ‘disconnect’ feature because you still had windows open? So far I have not found a simple way to do this – however I have created a script that helps with this. Unfortunately it still requires you to specify a list (input text file) of possible servers than needs to be checked. I would have liked if it was possible to have it automatically discover all machines with RDP sessions that use my logon…

The solution I created is simply a powershell script that takes an input text file with the list of machines you want to test connections with. This list would probably contain the usual machines you connect to. The other bit of the ‘solution’ is calling the ‘qwinsta’ utility that queries a specified machine and returns some details about RDP connections active on that machine. The script must then parse the output to figure out if your username is one of them…

param (
    $myAccount #(Read-Host -Prompt "Enter a user name")
    ,$serverListFile #(Read-Host -Prompt "File with list of servers")
)
if ($myAccount -eq $null){
    $myAccount = $env:username
}
if ($serverListFile -eq $null){
    $scriptpath = $MyInvocation.MyCommand.Path
    $dir = Split-Path $scriptpath
    $serverListFile = $dir + "\myservers.txt"
}

$showDebugInfo = $false
$reader = [System.IO.File]::OpenText($serverListFile)
"Listing RDP sessions for " + $myAccount
try {
    for(;;) {
        $server = $reader.ReadLine()
        if ($server -eq $null) { break }
        # process the line
        if(!(Test-Connection -Cn $server -BufferSize 16 -Count 1 -ea 0 -quiet))
        {
            " Error accessing " + $server
        }
        else
        {
            try
            {
                if ($showDebugInfo){
                    "Checking " + $server + "..."
                }
                $qwinstaResults = (qwinsta /server:$server | foreach { (($_.trim() -replace “\s+”,”,”))} | ConvertFrom-Csv)
                ForEach ($queryResult in $qwinstaResults)
                {
                    if ($queryResult -CMatch $myAccount)
                    {
                        "Logged on to " + $server
                    }
                }
            }
            catch
            {
                " Error processing " + $server
            }
        }
    }
}
finally {
    $reader.Close()
}
"Done"

Then using this script is as easy as calling it from (say) a batch file like this:

powershell.exe .\MyRDPConnections.ps1 myusername myservers.txt

Not perfect but at least it helps.

List AD users in a group using powershell

This is not a rocket science thing since there are apparently multiple ways to do this but I had a specific requirement to quickly get a list of users for a group but only list specific properties which include the “Title” property that is not available using the AccountManagement namespace.

So without mush further ado… here is a simple script that works for me.

# Read the input parameters $GroupName
param([string] $GroupName = $(throw write-host "Please specify the group name." -Foregroundcolor Red))

$Recurse = $true

$OutputFile = "GroupUsers.csv"
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$group=[System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ct, $GroupName)
$members = $group.GetMembers($Recurse)

$columnNames = "Name,Title,Description"
$columnNames | Out-File -FilePath $OutputFile -Encoding 'UTF8'
foreach($user in $members){
    $DE = $user.GetUnderlyingObject()
    $output = $user.name + "," + $DE.title + "," + $user.description

    Write-Host $output
    $output | Out-File -FilePath $OutputFile -Encoding 'UTF8' -append
    $output = ""
}

In order to get to the ‘Title’ property I use the ‘GetUnderlyingObject’ method to get the DirectoryEntry object which do have the ‘Title’ property. This way it makes it possible to access any/all the ‘forgotten’ properties which they never implemented in the new namespace.
Additionally it also save the output to a CSV file.