Recently on various occasions I was talking about consuming PowerShell remoting in a ways different than one that is usually used in demos. What you will usually see is connecting in good-old ssh fashion – using console window directly. One of two chapters I wrote for PowerShell Deep Dives book is about consuming PowerShell remoting using PowerShell Web Access. Today I would like to share practical implementation of other wonderful pair: PowerShell remoting constrained delegated endpoint and GUI script, that is build on top of it. It was used at my work for few months and because primary reason it was built for in first place is no longer valid – it will be removed soon.
Word of warning: I won’t explain what is remoting endpoint, what is constrained remoting endpoint, and what “delegation” means. I will move straight to the “meat” so if you want to find out more on that subject – I would strongly recommend starting with PowerShell help docs, or other external resources on that topic. You’ve been warned!
Problem and planned solution: GPOs were used to make sure certain AD groups are present in local admins group on each workstation. If that GPO for some reason failed – workstation would not get SCCM client installed. Most of people involved in troubleshooting SCCM client issues had no domain admin rights across all domains. We wanted to get rid of manual work done by people who actually had those admin rights. My suggestion was to use delegated constrained remoting endpoints and building simple GUI on top of it to make it very easy to use. Something like this:
I wanted to make it as simple as possible, and also minimize network load by adding some validation also on GUI side (green background in computer field is there for a reason), before any communication to the server would be initiated. But before I get to final solution – few words about how did I get there.
Why now?
PowerShell v3 makes creating constrained delegated endpoints a lot easier than it was in v2. In v2 days it was really deep dive topic. I was watching Aleksandar’s sessions about it and had really hard time following all the steps. It was also very hard to understand how configuration works by reading file that was used to define it. Also delegation was not supported by the product, so you had to use very clever tricks to get it going. In other words: if you were not as smart and fluent with remoting as my friend Aleksandar is, you were probably out of luck. V3 changes this dramatically. Configuration is relatively easy to create and maintain. Delegation is supported and easy to setup. I wouldn’t probably use it if the barrier wouldn’t be moved so low. So there I had it: easy way to create constrained endpoint with delegated credentials. Done and done! Or maybe not…?
Walking in chains
What is the problem with this approach? Constrained endpoints, by definition, are not very easy to use interactively. To be honest – there is no reason for them to be easy, they exist to expose certain commands normally not available to user that connects to them. And because they are running with alternate credentials – we do not want to give access to any command that isn’t necessary. Does it mean you are stuck in console with less than 10 commands and can’t do anything PowerShell-ish with results you get? Far from it. You obviously can do it like that, but PowerShell remoting is not designed to operate this way. It supports this, but has much more to offer than just good-old-ssh experience. And constrained endpoints are best served with implicit remoting.
Implicit remoting
When I first heard about it – it blew my mind. Suddenly I do things “over there” but I run commands like if I would do it “over here”. Implicit remoting is a way to “inject” remote commands in your local session and chain them together with any other cmdlet that you would like to use. Most of the time what you do does not require any special rights. Anybody can filter objects, process each object separately, export results to csv – list goes on. In delegated constrained endpoint this operations are usually not allowed, because assumption is that you don’t want anybody to perform them with delegated credentials. Implicit remoting is a way to destroy boundary of your session and let it connect to many endpoints at the same time. It makes also operations on different servers seamless: you can import sessions from different servers and move objects around between them. And if you can do anything what local session lets you do – you are one step away from dream world of many admins out there – clickable version of what you have so carefully created. You can’t run GUI on remoting endpoint, but with implicit remoting – you do not have to!
GUI-ing stuff
That was initial assumption when I was building tool for my work: I need to make it GUI-ed. My colleagues don’t use PowerShell as much as I do, and I see no reason to force them to use it. I quickly realized that with all that PowerShell remoting has to offer – I really do not have to. Next assumption was: it has to have as little dependencies as possible. At the end of the day, nobody wants to install anything just to get single, relatively simple tool to work. I was sure it will run on Windows 7, with .NET 3.5 on it, so I created GUI using WPF. I started with ShowUI during first design phase because it’s very easy to create skeleton with this tool. In the end – I’ve just used one of switches on ShowUI to convert it to XAML. Than I cleaned up said XAML of any pieces I did not need, and voila – my script was done. For actual actions I’ve used trick I blogged about not long ago.
Endpoint(s)
To make it more secure – my colleague insisted on one account/ endpoint per domain. All endpoints shared the same configuration. The only difference was account that we’ve used for RunAsCredentials and the name of configuration (that for ease of use matched the name of domain it would “cover”. Configuration would let you run only predefined list of proxies and two functions I’ve created and used in GUI: one to list members of local admins group on remote computer, and second one that would add groups. First one would let you specify remote computer as input parameter. Latter accepted only name of computer and domain (groups added would depend on this information). Why so strict? Even if someone would read GUI script and find out how to connect to the endpoint – he would not be able to do anything more than he could do with GUI. Computer name parameter was validated using internal workstation naming policy, and for command that was adding groups to local admins there was additional check for operating system – just to avoid unexpected server with name matching workstation’s standards. The whole configuration file (with less important bits removed):
@{ LanguageMode = 'NoLanguage' SessionType = 'RestrictedRemoteServer' FunctionDefinitions = @( @{ Name = 'Get-LocalAdmin' Options = 'ReadOnly' Scriptblock = { param ( [Parameter(Mandatory = $true)] [ValidatePattern('^[a-z]{3}\d{6}$')] $ComputerName ) $Admins = [ADSI]"WinNT://$ComputerName/Administrators,group" $Admins.Invoke('Members') | Foreach-Object { $_.GetType().InvokeMember( "Name", 'GetProperty', $null, $_, $null ) } } },@{ Name = 'Add-LocalAdmin' Options = 'ReadOnly' ScriptBlock = { param ( [Parameter(Mandatory = $true)] [ValidatePattern('^[a-z]{3}\d{6}$')] [string]$ComputerName, $Domain = 'FOO' ) $Options = @{ ComputerName = $ComputerName } if (!(Test-Connection @Options -Quiet)) { throw "Computer is not responding. Make sure it is on!" } if (!(Get-WmiObject @Options -Class Win32_OperatingSystem -Filter " Caption LIKE '%Windows 7%' OR Caption LIKE '%XP%'")) { throw "Computer is not a XP/ Windows 7 workstation!" } $Admins = [ADSI]"WinNT://$ComputerName/Administrators,group" try { $Admins.Invoke('Add',"WinNT://ROOT/HD-WW") } catch { "Error: $_ -> information that this groups is already member is possible." } try { $Admins.Invoke('Add',"WinNT://ROOT/HD-$Domain") } catch { "Error with adding domain group: $_" } } } ) }
GUI
As already mentioned I started with ShowUI for template, and than “translated” code to pure XAML. You can do it pretty easily yourself:
New-Window -SizeToContent WidthAndHeight { New-StackPanel -VerticalAlignment Center { New-TextBox -Text Test New-Button -Content Click } } -OutputXaml
That, plus trick to get all named controls into variables and I was good to go. Only part that was missing was code that actually performed the work. I could use implicit remoting, and normally I would. But this time around I had separate endpoint for each domain. Importing commands from all endpoints hardly made any sense to me, so decided to use Invoke-Command: performance was not important, and using this approach made implementation a lot easier. Final GUI code is relatively long, it includes a lot of code that would test conditions, react to errors, and more. The code that actually does the work is part of script blocks triggered when either of buttons are clicked. First, code that lists administrators already on the box:
try { $AdminsList.Items.Clear() $AdminsList.Items.Add("Administrators on computer $ComputerName :") Invoke-Command -ComputerName Endpoints -ConfigurationName $PredictDomain -ScriptBlock { param ($Computer) Get-LocalAdmin -ComputerName $Computer } -ArgumentList $ComputerName -ErrorAction Stop | ForEach-Object { $AdminsList.Items.Add($_) } $AdminsList.Items.Add( (New-Object System.Windows.Controls.ListBoxItem -Property @{ Content = "Done!" ForeGround = 'Green' } )) } catch { $AdminsList.Items.Add( (New-Object System.Windows.Controls.ListBoxItem -Property @{ Content = "Error: $_" Foreground = 'Red' } )) }
That was easy: we just needed to run the code remotely on selected endpoint, each one configured with domain admin rights in one of domains. This may work just fine without this rights, but no point in trying. Depending on results (success, error) we have different colour in our summary, making it obvious if operation succeeded or not. Once we know important groups are missing, we can do something about it:
try { Invoke-Command -ComputerName Endpoints -ConfigurationName $PredictDomain -ScriptBlock { param ($Computer, $Domain) Add-LocalAdmin -ComputerName $Computer -Domain $Domain } -ArgumentList $ComputerName, $DomainName -ErrorAction Stop $AdminsList.Items.Add( (New-Object System.Windows.Controls.ListBoxItem -Property @{ Content = 'Done!' Foreground = 'Green' }) ) } catch { $AdminsList.Items.Add( (New-Object System.Windows.Controls.ListBoxItem -Property @{ Content = "Error: $_" Foreground = 'Red' } )) }
As you can see in both cases I just call functions we defined on our remoting endpoint and pass parameters that basically should just work. GUI includes same validations that remoting endpoint, but this time around – I use things that only GUI interface can offer, like changing colours we’ve seen already:
So remember: just because you are using restricted remoting endpoints does not mean you have to work in constrained environment to get your job done: you can do whatever you want on machine that you own, and use remoting endpoint once you are happy with parameters you’re about to pass to it. Same with results: you do not have to take what owner of remoting endpoint offers, you can get results back and process them on your side. Including GUI PowerShell script both for getting results, and validating input parameters. And if you want to take a look at whole script – you can find it on PoshCode or Script Center. Enjoy!
Very Intresting Thank you!
Spice up your Blog Post with some needful Links!
Don Jones: Windows PowerShell: Implicit Remoting
http://technet.microsoft.com/en-us/magazine/ff720181.aspx
Jason Hofferle: Constrained PowerShell Endpoints
http://blogs.technet.com/b/heyscriptingguy/archive/2012/07/27/an-introduction-to-powershell-remoting-part-five-constrained-powershell-endpoints.aspx
Rob Greene: Windows PowerShell remoting and delegating user credentials
http://blogs.technet.com/b/askds/archive/2012/08/02/windows-powershell-remoting-and-delegating-user-credentials.aspx
cheers
Peter Kriegel
http://www.admin-source.de
Wow Nice code there! Slick GUI.
I wish my Sapien PowerShell Studio was working with WPF too.
Thanks for sharing