Tracking the State of PowerShell Runspaces in a Runspacepool

A question that I have received a number of times has always involved a way to determine the state of each runspace that is in a runspacepool. Since a runspacepool is used to throttle the number of runspaces that are able to run at a single time, it only makes sense that there should be a way to determine what runspaces are currently running and what ones are in a ‘NotStarting’ state. Knowing when a runspace is completed is simple with the IsCompleted property of the PowerShellAsyncResult object, but beyond that, it has been a mystery.

If you use my PoshRSJob module, you may have noticed that after you begin all of the RSJobs, that they all show a state of ‘Running’, which can create some confusion even though some of the jobs are not actually running just yet. Between this issue with my module and the number of questions that I had received, it has been one of my goals with runspaces to find a way to determine the state of each runspace in a runspacepool so others can make use of whatever technique that I could find as well as adding the discovered solution into my module.

The solution that I found begins with using reflection techniques to dig deeper into the PowerShell instance that we created and used with our runspacepool to find the state of a running pipeline and then looks at the IsCompleted property of the PowerShellAsyncResult object to determine whether that particular instance is in fact running or waiting to start in the runspacepool. in order to properly test this and ensure that I am not missing anything, I will have to have at least one runspace that has already been completed, one that is going to always be running and another one that should not be running yet.

To make this happen, I will kick off 6 runspaces within a runspacepool that will only allow a maximum of 2 concurrently running runspaces. The scriptblock for those will contain a While loop that will only break if the value being checked is an odd number. This means that at some point 2 runspaces will be completed,  2 will continue to run and another 2 will be waiting to start, but will never start due to the other jobs continuously running as they contain even numbers.

My runspacepool job code is below and uses the ideas that I described to ensure that I meet my goal of having runspaces in various states.

$RSP = [runspacefactory]::CreateRunspacePool(1,2)
$RSP.Open()

$List = New-Object System.Collections.ArrayList

$Result = 1..6 | ForEach {
    $PS = [powershell]::Create()
    $PS.RunspacePool = $RSP
    [void]$PS.AddScript({
        Param($i)
        While ($True) {
            Start-Sleep -Milliseconds 1000
            If ($i%2) {BREAK}
        }
    }).AddArgument($_)
    $List.Add(([pscustomobject]@{
        Id = $_
        PowerShell = $PS
        Handle = $PS.BeginInvoke()
    }))
}

 

You will notice that I saved the results of the BeginInvoke() command as well as the Id and PowerShell instance. This way we can accurately tell what values we are checking against as the Id matches the value of the If statement.

The contents of the list looks like this:

image

Based on my code in each runspace, we can easily deduce that Ids 1 and 3 should be completed while 2 and 4 should be running which leaves 5 and 6 as the remaining jobs left to run. a quick look at the Handle property of this object should show the IsCompleted property being True for 1 and 3 whereas the others will show as False.

image

This was the easy part. Knowing what has already finished isn’t what I set out to solve as it was already easy to find. I am now going to take a long look at Id 2 to look for a running pipeline which should give me an indication that there is something already running in the runspacepool.

In this example, we will look at Id 2 (which would be $list[1]) and will see if this is truly running like I am thinking it is.

The first thing I need to pull is the System.Management.Automation.PowerShell+Worker object from the PowerShell instance. This is not publicly available to view so we now begin our first dive into using reflection to get this object.

$Flag = 'static','nonpublic','instance'
$_Worker = $list[1].PowerShell.GetType().GetField('worker',$Flag)
$Worker = $_Worker.GetValue($list[1].PowerShell)

Using the proper flags to tell what kind of values we want for the Field, we can locate the nonpublic field and then take that object and pull the value using GetValue() and providing the original object that it belongs to, in this case the original object is the PowerShell instance. We can verify that we have the object and then move onto the next property to locate.

image

We are now halfway down our reflection rabbit hole. Taking this PowerShell+Worker object, we will go down one more level deeper and pull the CurrentlyRunningPipeline property out of this which will tell us if…you guessed it… if the runspace is currently running.

$_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flag)
$CRP = $_CRP.GetValue($Worker)
$CRP

image

Perfect! If there is data in this property, then that means that there is something running in the runspace and that therefore means the runspace in the runspacepool is currently running!

Ok, just a single test doesn’t really tell us that we are on the right path. We should definitely run this against a runspace that we know has already completed, such as Id 1 to check that piece and then run this against an Id that should not be running yet, like Id 5. If both of those do not have anything within the CurrentlyRunningPipeline property, then we can make a reasonable guess that we have a working approach to tracking runspace activity.

First, let’s run this against the completed runspace (Id1: $List[0]) and see what happens.

$Flag = 'static','nonpublic','instance'
$_Worker = $list[0].PowerShell.GetType().GetField('worker',$Flag)
$Worker = $_Worker.GetValue($list[0].PowerShell)

$_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flag)
$CRP = $_CRP.GetValue($Worker)
$CRP

image

No data in the property on the completed runspace. Now onto the runspace that we believe to be waiting to start (Id5: $list[4]).

$_Worker = $list[4].PowerShell.GetType().GetField('worker',$Flag)
$Worker = $_Worker.GetValue($list[4].PowerShell)

$_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flag)
$CRP = $_CRP.GetValue($Worker)
$CRP

image

Another successful test! Based on the results of both the completed and presumed pending runspace, we can see that we have a good way to determine the state of the runspaces. The only thing left to do is make sure that we know the difference between a completed and pending runspace and all we have to do is look at the IsCompleted property that we talked about earlier in the article. When it shows True and has no data in CurrentlyRunningPipeline, then it is a completed runspace but if the IsCompleted is False with no data in the other property, then we know that this hasn’t started yet.

The final script for my demo shows what I did to create the runspaces and then to determine the state of the runspaces and returning a ‘Running’, ‘NotStarted’ and ‘Completed’ state for each runspace.

$RSP = [runspacefactory]::CreateRunspacePool(1,2)
$RSP.Open()

$List = New-Object System.Collections.ArrayList

$Result = 1..6 | ForEach {
    $PS = [powershell]::Create()
    $PS.RunspacePool = $RSP
    [void]$PS.AddScript({
        Param($i)
        While ($True) {
            Start-Sleep -Milliseconds 1000
            If ($i%2) {BREAK}
        }
    }).AddArgument($_)
    $List.Add(([pscustomobject]@{
        Id = $_
        PowerShell = $PS
        Handle = $PS.BeginInvoke()
    }))
}
Start-Sleep -Seconds 2 
$Flag = 'static','nonpublic','instance' 
0..5 | ForEach {
    $_Worker = $list[0].PowerShell.GetType().GetField('worker',$Flag)
    $Worker = $_Worker.GetValue($list[$_].PowerShell)

    $_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flag)
    $CRP = $_CRP.GetValue($Worker)
    $State = If ($list[$_].handle.IsCompleted -AND -NOT [bool]$CRP) {
        'Completed'
    } 
    ElseIf (-NOT $list[$_].handle.IsCompleted -AND [bool]$CRP) {
        'Running'
    }
    ElseIf (-NOT $list[$_].handle.IsCompleted -AND -NOT [bool]$CRP) {
        'NotStarted'
    }
    [pscustomobject]@{
        Id = (([int]$_)+1)
        HandleComplete = $list[$_].handle.IsCompleted
        PipelineRunning = [bool]$CRP
        State = $State
    }
}

Running this code will produce the following output which will show the states for each runspace:

image

Note that I haven’t yet added this to PoshRSJob as I need to ensure that I add the appropriate code within the Type file within the module so the correct results are shown each time you run Get-RSJob. I will be sure to update this blog and post out to Twitter when that has occurred. Note that if you use this approach that you will either have to use this each time you want to view the status of the runspace, or you create a custom object with its own specific type and then supply your own type file (or use Update-TypeData) to automatically perform the checks each time you look at the object.

Posted in powershell | Tagged , , , | 1 Comment

Yet Another Way to Get Output from a Runspace, Reflection Edition

While working on some possible feature updates to my module, PoshRSJob, I have been looking at ways to get the output from a runspace without relying on the usual approach of using EndInvoke() while supplying a PowerShellAsyncResult object to pull the output. I had blogged about one way to get output from a runspace, but have found another way to grab the data as well, this time using reflection.

Now why am I continuing on this path to find more ways to get data from a runspace when there are already perfectly good ways to accomplish this? Well, one of my bigger struggles with making PoshRSJob more like the typical PSJobs that we all know and love are that it is able to preserve the streams properly whereas in PoshRSJob, we have to do some dancing to make it all work…almost. Write-Host continues to be a pain point as it is hard to find a good way to ensure that the stream is outputted not only properly, but with the proper colors defined when using the cmdlet. Now, I haven’t solved this yet and don’t plan on solving this any time soon, but by doing some experimenting, I have found some other ways that I wanted to share with you in viewing output from a runspace without ending the invocation of the runspace. My thoughts are that if I can find a different way to peek into a runspace to find if information is available,then I could refactor my code to provide a one time receive of the job to show all of the streams while still keeping the output objects available to until disposed of. Will this be solved? Maybe, but it is fun to keep digging into runspaces and this time, I get to do so using reflection! One of my favorite techniques to work with.

There really is not a lot to doing this, but we will cover from setup to tear down so you can easily copy and paste this for your own testing. First off, we build of and kick off the runspace like we usually do.

#Begin using the proces that we know and love
$PowerShell = [powershell]::Create()
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
$PowerShell.Runspace = $Runspace
[void]$PowerShell.AddScript({
    [pscustomobject]@{
        Data = 1
        Text = 'Test'
    }
})

$Handle = $PowerShell.BeginInvoke()

I am holding onto the PowerShellAsyncResult just out of habit, but in the end, we can just dispose of the runspace without calling EndInvoke(), or we can still call it and verify that the output lines up with what we will see using reflection. By the way, this is a great way to still get your output if you happen to forget the object in your travels.

#Specfiy the required flags to pull the output stream
$Flags = 'nonpublic','instance','static'
$Output = $PowerShell.GetType().GetProperty('OutputBuffer',$Flags)

image

Now that we have the OutputBuffer property object, we can then make use of the GetValue() method to pull the output data from the runspace. We will this by supplying the $PowerShell variable so it knows where and what to do to get the data it needs.

$Output.GetValue($PowerShell)

image

What this gives us is another way to maybe see if there is data available by looking at the count of the results and if there are more than 0, then we could proceed with processing of the runspace or just dispose of it without any worry. And in case you are wondering about the whole count approach, keep in mind that the actual object is of the System.Management.Automation.PSDataCollection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35]] type.

Get-Member -InputObject ($Output.GetValue($PowerShell))

image

Don’t call this method prematurely if the runspace is still processing data. This actually opens up the runspace to your current console session and will block any further input until the runspace has completed.

That is all for peeking into a completed runspace using reflection and PowerShell. While this will not likely be something that you would use often, it does provide an alternative means to looking at the data in a runspace without using EndInvoke().

Posted in powershell | Tagged , , | Leave a comment

Some Updates to Out-SquarifiedTreemap

It’s been close to a year since I published my Out-SquarifiedTreemap function (and blogged about it) after spending a number of weeks prior working on it to ensure that everything was accurate when used and also trying to figure out a good algorithm to handling the sizes of each square as well as the heatmap on it.

Fast forward those 11 months and I haven’t really done anything with it other than adding some code examples for those who might have been interested on the GitHub page. I use the code in the real world but I admit that the output is rather hard to read unless you use the tooltip when you hover over each square to see what the data actually is. Fortunately another individual by the name of Aaron Nelson (Blog | Twitter) who is a Data Platform MVP happened to see this and hit me up to ask about adding some features to it. Naturally I couldn’t resist the ideas for improvement and proceeded to implement a couple of I feel were much needed updates to the function.

Label for Each Square

Honestly, this one was way overdue to implement. Having the squares with their various sizes and colors for the heatmap gave a great view into how things shaped up, not having an ‘at a view’ glance due to no labels made the UI a little painful to read unless you hover over each square for its respective tooltip.

It took a little bit of time to figure out how I wanted to do this. I wanted something that would scale appropriately depending on the size of the square. That meant that I didn’t want to worry about the font size at all and didn’t want it to look like it was constrained to its own text box. The Label control jumped out at me because doesn’t show up as though it is in a box and looking like it was just typed in.  This works nicely until you try to use this in a Canvas layout. My testing was done using a Grid layout control which makes scaling of the text simple whereas a Canvas is the lowest form of a layout as the user has complete control of where the child controls can be placed and sized with pretty much no constraints at all.

This was a problem. I didn’t want a label that didn’t scale properly…or at all for that matter. Enter the ViewBox control which allows me to place the label inside of it and lets me scale treat the Label as though it was in the Grid by scaling the text based on the size of each square it is in. Perfect! Using these controls,  I can then create the control and then just size it as the same size as my rectangle that shows off each item and place it within the same coordinates as the rectangle so it appears like it is the same thing. The last thing that I needed to do was to turn off the hit detection on the Viewbox and Label so only the Rectangle would be detected when clicked on (which plays a major role in the –PassThru support). The code that handles the creation of the Label and Viewbox is here:

Function New-ViewBox {
    Param(
        $Width,
        $Height,
        $Text
    )
    $Label = new-object System.Windows.Controls.Label
    $Viewbox = new-object System.Windows.Controls.Viewbox
    $DropShadow = New-Object System.Windows.Media.Effects.DropShadowEffect
    $DropShadow.Opacity = 5
    $DropShadow.BlurRadius = 5
    $DropShadow.Color = 'Black'
    $DropShadow.ShadowDepth = 0
    $Viewbox.Stretch = 'Uniform'
    $Viewbox.Width = $Width
    $Viewbox.Height = $Height
    $Label.IsHitTestVisible = $False
    $Label.FontFamily = 'Calibri'
    $Label.FontWeight = 'Bold'
    $Label.Foreground = 'White'
    $Label.Content = $Text
    $Label.Effect = $DropShadow
    $Viewbox.AddChild($Label)
    Return $Viewbox
}  

I can call it in the same way that I called my New-Rectangle function and place it in the same location on the canvas.

If ($ShowLabel) {                
    $Viewbox = New-ViewBox -Width $_.Width -Height $_.Height -Text $_.$ShowLabel
    [void]$Canvas.Children.Add($Viewbox) 
    [System.Windows.Controls.Canvas]::SetLeft($Viewbox,$_.Coordinate.X)
    [System.Windows.Controls.Canvas]::SetTop($Viewbox,$_.Coordinate.Y)
}

Ah yes, the –ShowLabel parameter is what will make up whether the label is actually shown as well as what type of label will be displayed on the UI. Currently, there are 3 possible values that you can use: LabelProperty, DataProperty and HeatmapProperty. You might notice that these are named after the same parameters that already exist in the function. They are directly related and will display the respective label on the UI when used.

$Tooltip = {
@"
Process Name <PID>:   $($This.LabelProperty) <$($This.ObjectData.Id)>     
WorkingSet Memory(MB): $([math]::Round(($This.DataProperty/1MB),2))
"@
}
Get-Process | Sort-Object -prop WS -Descending | Select -First 8 | 
Out-SquarifiedTreeMap -Tooltip $Tooltip -LabelProperty ProcessName -DataProperty WS -HeatmapProperty WS -Width 600 -Height 400 -ShowLabel LabelProperty

image

 

Looks like Chrome is definitely a memory hog based on the UI being displayed. As you can see, the text does some nice scaling based on the size of each rectangle. Naturally,the smaller the box the smaller the text, so at that point you will want to rely on the tooltip for those cases.

Providing PassThru Support

Next up is providing PassThru support so that if you wanted to do more than just view the pretty UI, you can specify the –PassThru parameter and then pick an item that you want to do more with so that it will pass through the original object that was sent to the UI. Pretty handy if you wanted to stop the process with the most memory utilization or needed to do more with some files or folders or anything else that you can think of! Setting this up meant that I had to specify a command to run when one of the rectangles has been clicked on. One problem that I ran into was that I am already tracking when I click on the mouse button to drag the UI around with the mouse. This gets thrown out of the window when using –PassThru and if you did want to move the window around, you need to be holding down on one of the CTRL buttons before attempting to click and move it.

Some of the code that sets all of this up is below:

#region TabControl event handler
[System.Windows.RoutedEventHandler]$Global:RectangleKeyDownChangeHandler = { 
    Write-Verbose "[KeyDwnhandler-CTRLKeyDown] $($Script:KeyDown)"
    If ($Script:KeyDown) {
        Try {
            Write-Verbose "[KEYUP] DragMove"
            $Window.DragMove()
        }
        Catch {Write-Warning $_}
    }
}
$Window.AddHandler([System.Windows.Shapes.Rectangle]::MouseLeftButtonUpEvent, $RectangleKeyDownChangeHandler)

[System.Windows.RoutedEventHandler]$Global:RectangleKeyUpChangeHandler = { 
    If ($Script:IsPassThru -AND -NOT $Script:KeyDown) {               
        If ($_.OriginalSource -is [System.Windows.Shapes.Rectangle]) { 
            $Source = $_.OriginalSource           
            $Script:Result = $DataHash.TreeMapData | Where {
                $_.Tag -eq $Source.Tag
            } | Select-Object -ExpandProperty ObjectData
            $Window.Close()
        }
    }
}
$Window.AddHandler([System.Windows.Shapes.Rectangle]::MouseLeftButtonUpEvent, $RectangleKeyUpChangeHandler)
#endregion TabControl event handler

This code sets up the event handlers for each rectangle by making use of the bubble up eventing so that regardless of which rectangle is clicked, the handler will fire and proceed with the appropriate actions.

To handle the output when you click the UI, I have a piece of code at the end that runs after the UI itself is closed. This ensures that if there was a selection picked, that it would actually send the object onto the next command.

#Show UI
Write-Verbose "[END] Show UI"
[void]$Window.ShowDialog()
Write-Verbose "[END] UI Close"
If ($IsPassThru) {
    Write-Verbose "Output Object"
    $Result
}

I’ll make use of the same code earlier but now I will add the –PassThru piece so I could attempt to stop a process that was appearing to be hogging up my much needed memory.

$Tooltip = {
@"
Process Name <PID>:   $($This.LabelProperty) <$($This.ObjectData.Id)>     
WorkingSet Memory(MB): $([math]::Round(($This.DataProperty/1MB),2))
"@
}
Get-Process | Sort-Object -prop WS -Descending | Select -First 8 | 
Out-SquarifiedTreeMap -Tooltip $Tooltip -LabelProperty ProcessName -DataProperty WS -HeatmapProperty WS -Width 600 -Height 400 `
-PassThru -ShowLabel LabelProperty | 
Stop-Process –WhatIf

SqTreemapPassThruDemo

So with that, there are a few new things that have been added to my function that will hopefully make using this UI a little more useful than previously.

As stated earlier, you can find this function over at GitHub: https://github.com/proxb/SquarifiedTreemap

And as always, feel free to let me know of new features that you would like to see or go ahead and submit a Pull Request and let me see what you have in mind!

Posted in powershell | Tagged , , , | Leave a comment

Setting up Local Administrator Password Solution (LAPS)

I decided to spend some time implementing LAPS in my lab as it is Microsoft’s solution to local administrator account password management. Why would I want something like this in my environment? Great question! Most organizations probably use the same password (maybe a slightly modified password based on each client…maybe) that ensures that the people who help manage the workstations have a way to log into the system should the computer lose its network configuration or some other issue where the only way to troubleshoot might he to log into the workstation using the local administrator account.

This is great until you someone such as an insider threat manages to get control of the password to the administrator account and now has a way to laterally move around the network from host to host until they can elevate their account to a domain admin. And at that point we know it is game over.

You might be saying “Boe,WTF?!? You are a PowerShell guy so why are you talking about something like this?”. Well, we are all responsible for securing our environment and this provides a great way to do so. Plus, there are PowerShell commands available to manage this service which makes it doubly awesome! Smile

Download the LAPS Install File and Review

You can find the files available to download from the following site: https://www.microsoft.com/en-us/download/details.aspx?id=46899

Included are some great documents that show how to setup and configure LAPS such as using Group Policy as well as extending the schema for the two (2) new attributes that are required for LAPS. In one of these attributes (ms-Mcs-AdmPwd) on each computer object you will find the password (!) for the local administrator account. Before you become too alarmed, these are called “Confidential Attributes” meaning that the attributes are protected by ACLs which are only accessible by the Domain Admins group and any other group that you specify. Standard users (who have Read access to AD) will not be able to view the attributes data and will only see blank information. Only the computer account can write to this location as well as to the other attribute (ms-Mcs-AdmPwdExpirationTime) which is used to determine if the password has expired and needs to be changed. With this in mind, any group that you provide access to the attributes should be monitored to ensure only those that need access, have access.

Installing LAPS Management

I am going to get things started by installing the management tools on one of my servers which includes the following items:

  • Client UI
  • PowerShell Module
  • GPO Templates

The process for the installation is pretty simple; all I do is double click on the msi file (LAPSx64.msi in this case) and run through the wizard, selecting all of the components for installation.

image

Once that is done, we can then move on to updating the schema by adding the two attributes that we talked about earlier using the AdmPwd.PS module that we just installed. But before we update the schema, now is a great time to review the module!

Reviewing the AdmPwd.PS Module

As mentioned, we can do a lot of our management via the AdmPwd.PS module for PowerShell. Yea, the name of the module probably could have been called LAPS in my own opinion, but that is all right Winking smile

Let’s take a quick look at the available cmdlets on this module.

Get-Command –Module AdmPwd.PS

image

Here we see that there our cmdlet to update the schema is called Update-AdmPwdSchema as well as other cmdlets to locate the password for a particular system, resetting a password and setting some permissions to for a variety of things.

Updating the Schema using PowerShell

Now that we know what commands are available to use, we should move forward in our configuration and get the schema updated so our computer account objects have the required attributes. How do we use this cmdlet? Time to check out the help file to learn what needs to be done.

Get-Help Update-AdmPwdADSchema

image

Well…there really isn’t much here to do at all. We simply just run the command and it should “just work”. Nothing wrong with that at all. The one thing that is a little odd is the lack of a –WhatIf parameter to simulate what would happen if we ran this. How do I know this?

image

Not really a major issue by any means, but typically if a command performs any change then it should use –WhatIf as part of its parameters.

I’ll go ahead and run this while using –Verbose just to see if we happen to have any sort of output.

Update-AdmPwdADSchema –Verbose

image

Whoops! Remember folks, if you are updating the Schema, you must be a member of Schema Admins first! Simply add your account to the group, log out and log back in and make the update.

image

image

No verbose output here, but we do get object output showing that the update was successful! Now we remove our account from Schema Admins and log out and back in and then move on to our next part.

image

Watching out for Extended Rights

The documentation mentions that you should be aware of any group or user that has the All Extended Rights access under an OU which has a computer account as they will be able to read the confidential attribute value meaning that they have access to the password. The accounts/groups that have this permission and do not need them should have it removed to prevent unauthorized access.

We can quickly create a report on what accounts and groups have this by using the Find-AdmPwdExtendedRights cmdlet. To make this even more useful, we can combine the output of Get-ADOrganizationUnit from the ActiveDirectory module and then massage the object that is outputted to see all of the accounts and groups with this permission.

Get-ADOrganizationalUnit -Filter *|Find-AdmPwdExtendedRights -PipelineVariable OU |ForEach{
$_.ExtendedRightHolders|ForEach{
[pscustomobject]@{
OU=$Ou.ObjectDN
Object = $_
}
}
}

image

Optionally, you could filter out System and Domain Admins if you wanted to make locating potential unauthorized accounts and groups easier to locate. In this case, I have nothing that concerns me at the moment but it is something that would be a good scheduled task to run daily to ensure nothing suspicious might be happening.

Installing the Client Side Extensions

Before we go too much deeper into setting up the permissions, we should look at installing the client side extensions on each client. In a larger environment you would probably use something like SCCM to install this on all of the systems. In my case, I am going the group policy route and will configure a GPO that will install the LAPSx64.msi file onto each system.

image

I really didn’t do anything special here other than just point this deployment package to a UNC path that was reachable by everyone on the network.

Once that has been completed and I assigned the GPO to my workstations OU. I proceeded to reboot the client and see what happens in hope that it installs correctly. Of course, that didn’t happen as I had package errors with error codes of %%2 and %%1274

image

Turns out the fix for this was to increase the GPO startup processing time to 30 seconds by going to the Computer Configuration>Administrative Templates>System>Group Policy and locating the Specify startup policy processing wait time and setting the value for this to 30 (seconds). Reboot the client and we now have a successful installation!

image

image

All ready to go now! As of right now, we haven’t actually enabled LAPS to begin setting the local administrator passwords just yet. We just need to work on some extra permissions and configure the group policy to enable and configure how LAPS will set the passwords.

Optional Installation

If you wanted a command line approach to installing the client side extension, you can use one of the following approaches to install it.

x64 machines

msiexec /q /i \\server\share\LAPS.x64.msi

x86 machines

msiexec /q /i \\server\share\LAPS.x86.msi

Deploy and Create an optional local administrator account

msiexec /q /i \\server\share\LAPS.x86.msi CUSTOMADMINNAME=NewLocalAdmin

Configure Permissions for the Computer to Update its Attributes

Next up is to ensure that the systems which will be managed by LAPS will be able to update the new attributes on their active directory computer account object. To make this process as easy as we can, we will make use of the Set-AdmPwdComputerSelfPermission cmdlet which we can assign to the OU where the computer accounts reside at. Using this cmdlet is simple: simply point it towards an OU and it will assign the necessary permissions.

Set-AdmPwdComputerSelfPermission –Identity ManagedWorkstations –Verbose

image

This will ensure that this OU and any sub-OU will assign SELF the ability to update the new attributes that we have added to the computer object.

Unfortunately, there isn’t a Remove-AdmPwdComputerSelfPermission cmdlet that can remove this permission from an OU. So if you need to perform this action, you will have to do it another way.

Granting Rights to User or Groups to Read and Reset the Password

With this completed, out next step is to configure the rights for a user (or members of a group) to have the rights to read the password from the confidential attributes as well as writing to the password expiration date attribute. Note that there are two (2) cmdlets that will set this up:

  • Set-AdmPwdReadPasswordPermission
  • Set-AdmPwdResetPasswordPermission

These cmdlets will allow you to set the permissions on a given OU (or OUs) to delegate the necessary permissions to either view the password or write to the ms-Mcs-AdmPwdExpirationTime attribute which will allow for the resetting of the system’s password.

I am going to grant my WorkstationPwdAdmins the ability to read and reset the password on the computers under the ManagedWorkstation OU.

Set-AdmPwdResetPasswordPermission –Identity ManagedWorkstations –AllowedPrincipals rivendell.local\WorkstationPwdAdmins –Verbose

Set-AdmPwdReadPasswordPermission –Identity ManagedWorkstations –AllowedPrincipals rivendell.local\WorkstationPwdAdmins –Verbose

image

At this point we are pretty much ready to go! All that is left to do is enable LAPS via our GPO and make a couple more configurations to set the password policy and we will be able to give this a spin and see if it works!

Configuring the GPO to Enable and Set Password Policy

First off, we need to ensure that whatever system we are going to be updating the policy on has the AdmPwd.admx file located under C:\Windows\PolicyDefinitions and the AdmPwd.adml under C:\Windows\PolicyDefinitions\en-us folder. That way when we open up the GPO, we can find LAPS under Computer configuration>Administrative Templates.

image

From here I will set the Password Settings first.

image

I’ve opted to keep password complexity at the default of using Upper and Lower case lettings, numbers and special characters. For the Password length and Password age, you should check your domain password policy to ensure that you are not stepping out of bounds. Setting a password length that is shorter than what is allowed will cause the process to fail.

image

Since I do not have a second administrator account that is locally on my systems, I am going to leave this in an non-configured state. By default, LAPS will reset the password on the Administrators account by looking at the well known SID but we do have the option of being able to reset a second administrator account if we choose to do so.

image

Here I want to ensure that if a system has gone longer than the password expiration setting then it will be changed immediately.

image

At this point, I am telling LAPS to begin management of the local administrator account passwords. Once this is set, the next time that group policy refreshes on the local systems, their password will be reset.

Validating that the Password is being Managed

So far, we can see that the password is not being managed by looking for the two attributes from my account which has the necessary rights.

image

I’ll initiate a group policy refresh on the client and then we should see the attributes show up with values.

Invoke-GPUpdate –Computer Client1A –Verbose

After a short wait, we will re-run our ADComputer cmdlet again and see what shows up now.

SNAGHTML1f165a51

My clients are now being managed by LAPS and I can view the password as well with my admin account. So what happens if an account that doesn’t have the rights to view the password tries the same command? Let’s find out!

SNAGHTML1f18e736

While the account can tell when the password expires next, they cannot read the password as this particular account doesn’t have access to the confidential attribute.

So what about that datestamp for the expiration time of the password? How in the world can we read this thing? Simple! We make use of the FromFileTime() method of [DateTime] and supply that string as the parameter and we will  instantly know when the password will expired and be reset.

$Computer = (Get-ADComputer –Identity client1a -prop ms-Mcs-AdmPwd,ms-Mcs-AdmPwdExpirationTime)
[datetime]::FromFileTime($Computer."ms-Mcs-AdmPwdExpirationTime")

image

Of course, there is always an easier way to accomplish things like this in PowerShell and the way here comes courtesy of the Get-AdmPwdPassword cmdlet. The nice thing here is that the password is also converted for us to a more human readable time.

Get-AdmPwdPassword –Computername Client1A

image

 

Forcing a Password Reset

What if something happens and we need to force a password reset on a local system? Yes, there is the fat client that you can use, but we know that every is better in the PowerShell console, right? With the ActiveDirectory module and Set-ADComputer cmdlet,  we can quickly knock out a password reset on 1 or more systems.

The key to this is being able to change the data in the ms-Mcs-AdmPwdExpirationTime attribute. Remember that the value translates to a date down the road that the machine will read upon its group policy refresh and determine if it is time to reset the admin account password. With that in mind,we can just set that value to 0 which will then initiate the process at the next policy refresh.

Set-ADComputer –Identity Client1A –Replace @{"ms-Mcs-AdmPwdExpirationTime"=0}

A quick check of the object shows that it has been set to 0.

image

We aren’t done yet. As I have mentioned a couple of times, this only sets the attribute but doesn’t actually initiate anything until the group policy is refreshed. So with that I will initiate the refresh using Invoke-GPUpdate against the client and we should then see the attribute update with a new expiration time.

image

And now we can see that the password was reset. We can further verify this by comparing it against the previous password that I had shown earlier.

image

Now this is a great approach when you are on a different system where you might not have the AdmPwd.PS module available, but if you do happen to have it, well then you get the benefit of using Reset-AdmPwdPassword cmdlet at your disposal! You can supply a computername and the date when the password should expire (called WhenEffective) and you will have the same results that we had with the previous example with the expectation that you will force a policy refresh after.

Reset-AdmPwdPassword –Computer Client1A –WhenEffective (Get-Date).AddDays(-1)

By specifying yesterdays date, the computer will see that the time has expired and will then begin the process of resetting the password.

You can definitely perform the same actions by using the client tool as we have done in PowerShell but we will still have the same results of needing the policy to be refreshed manually.

image

Personally, I thing that both the UI and the cmdlet in the AdmPwd.PS module should have a way to force policy to refresh (maybe as an additional parameter) and the UI could use a checkbox or something similar. An issue with the UI is that it only works with a single machine at a time, so scaling that out to your enterprise may not work too well.

So that wraps up my post on installing and configuring LAPS in your environment and using PowerShell as a means to accomplish most of that configuration. If haven’t considered a means to keep your local administrator passwords different from one another and need an automated approach, then I would recommend that you give LAPS a try.

References

https://adsecurity.org/?p=1790

https://www.microsoft.com/en-us/download/details.aspx?id=46899

https://technet.microsoft.com/en-us/library/security/3062591.aspx?f=255&MSPPError=-2147217396

Posted in powershell | Tagged , , | 11 Comments

Renewed as a Cloud and Datacenter Management MVP!

image

I have been grateful once again to be renewed as a Microsoft MVP in Cloud and Datacenter Management for 2016. This is an honor that is never guaranteed and I am always humbled to receive it.

This is something that doesn’t come easily as we are evaluated each year with the same folks who are trying to get into the program. The work is always fun and I know that this award would not have been possible if not for the community itself, both in viewing and using the things that I have posted as well as providing great feedback to me so I can improve on not only things that I write about, but also the tools that I present for others to use.

It has definitely been an amazing year for the PowerShell community with PowerShell going open source and being made available on other operating systems such as Linux and MacOS. Seeing such an increase on the use of PowerShell in the InfoSec community has also been amazing as well as the many cool and unique things that others have been working on and showing off just shows that PowerShell is not going away at all. It is only becoming bigger and more important to learn for those in our field.

So with that I say Thank You to the community and I cannot wait to see what the next year brings!

Posted in powershell | Tagged , , , | 2 Comments