Granular access via PowerShell Remoting

Broad access definitions can be a pain.  Let’s say we have an important service, where we can provide support access to all administrative commands, or none.  Which is better:

  • Providing your Support staff access to tools they need to do their job
  • Locking down access to tools which allow a single support employee to compromise the integrity or availability of an entire service

I would like both.  Thanks to PowerShell remoting, we can achieve this through delegated, constrained endpoints.  High level details from an example:

  • Some technology might not let me provide access to a single command, or control which parameters are passed to the command.  I can only pick between ‘admin’ and ‘not admin’, and I can’t control the scope of these.
  • Install Windows Management Framework 3 and enable PowerShell remoting on the applicable server
  • Create a service account with the permissions to accomplish the task at hand.
  • Create a startup script that restricts a session to whitelist the specific commands we want.  Perhaps wrap these commands so that certain parameters are controlled via ValidateSet.  Perhaps add logging.
  • Create a PS Session Configuration that uses the service account and startup script we created, that only accepts connections from a security group your support staff belong to
  • If desired, provide support with a GUI, PowerShell function, or instructions on using the endpoint
  • Document everything.

With the setup above, support and only support has access to a PowerShell endpoint that runs with the necessary privileges that support staff do not have themselves, and that only has access to the specific commands we defined.  In other words, we created granular access to a service that does not provide this on its own.

Setting this up involves taking a few steps, but once you understand what is going on, it is very easy to repeat and extend this functionality to other use cases.

Please reference the following guides for more details on PowerShell remoting in general, as I glance over this topic:

Let’s walk through the steps.

Prerequisites – Windows Management Framework 3, PSRemoting, a RunAs account

  • If we want to use a delegated endpoint (one that always runs with a pre-specified credential), we need to use Windows Management Framework 3.  If you are running Server 2008 or Server 2008 R2, this means you.  Server 2012 and later include this functionality out of the box.
  • If you are running Server 2008 or Server 2008 R2, enable PS Remoting.  Generally, run Enable-PSRemoting –confirm:$false –force.  If you want to dive deeper into using SSL, setting this up automatically with Group Policy, or other details, consult the guides or Internet.  Again, Server 2012 and later include this functionality out of the box.
  • If you want to use a delegated endpoint, create or identify the account the session will always run as.

Creating a startup script

There are several ways we can constrain a session.  I will be using a startup script given the flexibility this provides.  You could also use New-PSSessionConfigurationFile to control LanguageMode, visibility to cmdlets/functions/aliases, add function definitions and modules to import, and make other customizations.

Much of this script is thanks to the fantastic Windows PowerShell in Action 2nd edition.  It’s a little out of date, but still very relevant and helpful.  If you have any coding or scripting experience, I would recommend this book over most others.

Please download the SpoolService.ps1 demo script from the Technet Script Gallery.  This is a demo startup script and includes brief notes on implementation.

A few quick notes!

  • You might not need to create these endpoints on each and every server.  Perhaps you could designate one or a handful of servers to host these endpoints.
  • You could create multiple endpoints, or consolidate startup scripts and get creative; use the Active Directory module to check for security groups of the connected user ($PSSenderInfo.connectedUser) and customize the startup script and function definitions based on those groups.
  • You don’t need to worry about the double hop problem for the first hop to a delegated session, so feel free to store startup scripts on a network share – just don’t let your end users modify the contents!
  • I have a few functions in the startup script that work together to provide rudimentary logging: Start-Log, Get-HistoryLast, Write-Log, Get-MyCommand, and Check-InvokeCommand.  If you have any suggestions on improving this please let me know! EDIT: Use real logging solutions, not these : )

Register the session configuration

The hard work is done!  We just need to register the configuration and provide details on how to use it.  We will use Register-PSSessionConfiguration:

Register-PSSessionConfiguration -name "SpoolService" -StartupScript "\\path\to\SpoolService.ps1" -RunAsCredential $(Get-Credential) –ShowSecurityDescriptorUI

Details on these arguments:

  • Name – This is the name you will refer to when connecting to the endpoint.  I try to keep the name and the startupScript filename consistent and meaningful.
  • StartupScript – Path to the startup script that will run, which you can use to lock down the session.  Note that this doesn’t suck in and make the script permanent.  You can modify the script and the changes will take effect the next time someone connects to this endpoint.
  • RunAsCredential – Always run these sessions with credentials for this account.
  • ShowSecurityDescriptorUI – Pop up a GUI to control access to this endpoint.  Provide the security group that needs to connect to this group with Execute(Invoke) permissions.  You might want to reconsider access for the local Administrators group.  For the ambitious, feel free to use the SecurityDescriptorSddl parameter.

Test the endpoint and inform the end users

At this point, we can test the endpoint to confirm everything is working as desired.

Invoke-Command -computername $server -configurationname SpoolService -scriptblock { Restart-Service -name AppIDSVC }

Check the contents of the log file that was created in $logPath (C:\temp until you change it).  There is no simple way to log things that run via Invoke-Command that I could find.  If you have ideas or better luck with this, please let me know!

You could now create a GUI that uses this endpoint to your end users, write them a function that runs against this endpoint, or provide them details on how to access this endpoint with invoke-command, enter-pssession, or some other method.

Get creative!

That’s about it – for any technology exposed via PowerShell or the command line, you should now have the ability to roll your own granular access controls.

I provided a very rudimentary startup script.  You could get very creative with this.  Be sure to keep things documented.  I would recommend tracking the following for every endpoint:

  • Endpoint name
  • Server(s) hosting the endpoint
  • RunAs account for the endpoint
  • Startup Script for the endpoint
  • Security groups with access to the endpoint
  • Path to logging for the endpoint, if applicable
  • Other details as necessary

EDIT:

A number of resources and solutions have been published since this post. Be sure to check them out!

  • Boe Prox’ series on remote endpoints
  • Microsoft’s JitJea keep in mind this is currently limited to local accounts. Ping Microsoft on this if you think it would be worth including domain accounts : )

Cheers!

Learning and Exploring PowerShell

PowerShell is a great window into the world of Microsoft and other PowerShell enabled technologies.  You can use it on the fly or as a framework to automate various processes.  You can build tools for IT or end users that reach across technologies to provide a consistent, flexible solution.  Before and while doing this, you will need to know how to explore and get help within PowerShell.

A quick tip for anyone not using Windows 8 / Server 2012 or later:  Download and install Windows Management Framework 3.  This includes the updated PowerShell ISE, which is fantastic for every day use and for learning, with features like Intellisense, GUI command discovery, and ctrl-c / ctrl-v compatibility.

Discovering Commands

PowerShell has many commands.  You could easily end up with thousands of commands in your session; how do you know what to run?

The following commands will help you discover which commands are available.  Once you have things narrowed down, you might need to hop on the web to find out which command is appropriate for your scenario.

#List all commands.  In PowerShell 3, this covers commands in modules and snapins that you haven’t added yet.
Get Command

#List all commands, including external executables in the PATH variable.
Get-Command –name *

#List all commands loaded in the current session (PowerShell 3)
Get-Command –ListImported

#List all modules available, all PSSnapins available
Get-Module -ListAvailable
Get-PSSnapin -Registered

#List all commands from the PowerCLI snapin and the Active Directory module
Get-Command -Module VMware.VimAutomation.Core, ActiveDirectory

#List all commands that start with Convert
Get-Command -name Convert*

#List all commands with VM anywhere in the name
Get-Command –name *VM*

#List all commands with the verb ConvertTo or Get.  Use -noun to search for nouns
Get-Command -verb ConvertTo, Get

#List all commands that take the parameter ComputerName
Get-Command -parametername ComputerName

Getting help for a command

At this point, you should have an idea of one or a handful of commands that could meet your needs.  Use the built in help system or Internet to find out how to use these or to pick which command is appropriate for your use case.

This can be difficult.  There is more than one way to skin a cat, and there are many, many PowerShell users out there who try to be helpful, but might end up posting inefficient or outdated code.  Maybe they tried translating VBScript to PowerShell, didn’t realize there was already a command for the code they wrote, or didn’t think through the execution of their code.

A quick note – Starting with PowerShell 3, you will want to run Update-Help, or just confirm that you want to download the updated help when you first run Get-Help. This needs to be done as an administrator unfortunately.

#Pull up a browser with the latest help info for Get-ChildItem - note this will only work with commands where HelpUri is defined
Get-Help Get-ChildItem –online

#Pull up all help details for Get-Command - full details, parameter info, examples, etc.
Get-Help Get-Command –full

#List all about_ help articles.  These describe the language and other higher level concepts than individual commands
Get-Help about_*

Important note:  If you are having trouble determining what information to provide as parameters, or how to pipe information to a command, –full will provide these details for every parameter.  Is the parameter required?  Is it a positional parameter?  What is the default value if none is provided?  Will the parameter take information from the pipeline, and will it take the entire object from the pipeline, or by a property of that object?

#Pull up examples for Get-ChildItem
Get-Help Get-ChildItem –Examples

#Get help from a script that uses comment based help.  Show only the basic help info
Get-Help \\path\to\script.ps1

In the following function I pull details from get-help such as ‘Synopsis’ for every command in a certain module.  If you use a custom module with many commands, it can be helpful to break them down by category.  I use ‘Functionality’ from comment based help.

function Get-HelpDetails {
            <#
              .SYNOPSIS
              Get details from comment based help for specified commands
              .DESCRIPTION
              Get details from comment based help for specified commands
              .PARAMETER module
              Return details from all commands in specified modules              .PARAMETER command
              Return details from specific commands listed here              .PARAMETER properties
              Return these properties from the comment based help.  Examples: description, name, category, synopsis, component, role, functionality
              .EXAMPLE
              Get-HelpDetails -module ActiveDirectory -properties Name, Synopsis | sort Name | Format-Table -autosize
              Get the Name and Synopsis for all commands in the ActiveDirectory module.  Sort by name, format as autosized table
              .EXAMPLE
              Get-HelpDetails -module CustomModule -properties Functionality, Name, Synopsis | sort Name | Format-Table -autosize
              Get the Funtionality, Name, and Synopsis for all commands in the CustomModule module.  Sort by Functionality, then name.  Format as autosized table
              .FUNCTIONALITY
              General Command
              #>
            [cmdletbinding()]
            param(
                [string[]]$module = $null,
                [string[]]$command = $null,
                [string[]]$properties = @( "Name", "Synopsis" )
            )
            #define parameter hash for Get-Command function
            $params = @{}

            if($module){

                #Build hash table for get-command module parameter
                $params += @{module = $module}

            }
            if($command){

                #Build hash table for get-command name parameter
                $params += @{name = $command}

            }

            #Run get-command with parameters from above, only return name
            $commands = Get-Command @params | select -ExpandProperty name

            #Loop through each command and return selected properties
            foreach($cmd in $commands){
                Get-Help -name $cmd | select $properties
            }
        }

        #Run the command to view functionality, name, and synopsis for SomeCustomModule module
        Get-HelpDetails -module SomeCustomModule | sort functionality, name, synopsis

In the following example, I will return the code of the Get-HelpDetails function we just created, which can be helpful if you are curious about the inner workings of a command, or want to quickly reference code.  I use this in the Open-ISEFunction function, which opens a function in a new ISE window.  Please note that this will only work where the definition is available (e.g. scripts, functions)

Get-Command Get-HelpDetails | Select -ExpandProperty Definition

Exploring the output of commands

We have our command, and know how to use it; how do we know what we can work with when we run it?  Get-Member, Select-Object and Format-List are helpful tools for this!

A quick note:  A property is information about an object.  The name of a process.  The start time of a process. etc.  A method is a predefined way to interact with an object.  You can ‘kill’ a process or ‘stop’ a service.

#First, let's just take a look at the typical results from Get-Process; we should see properties like Handles, NPM(K) and ProcessName for each process
Get-Process

#Find out what properties and methods are available from Get-Process.  There is a huge list of other properties that we can obtain and methods we can use against these objects
Get-process | Get-Member

#Some properties can be objects or arrays themselves.  To expand and look further into a property, use the Select-Object -expandproperty <property> command.  Here, we look at modules for any processes matching PowerShell*
get-process powershell* | select -ExpandProperty modules

#We can keep going!  Let's look at everything that comes back from the first module, using get-member to see the properties and methods
Get-Process powershell* | select -ExpandProperty modules | Get-Member

#If you want to see examples of actual values for the various properties, use select -property * or format-list -property *.  We select the first process and module in these examples to limit the results
Get-Process powershell* | select -ExpandProperty modules -first 1 | select -First 1 | Format-List -Property *
Get-Process powershell* | select -ExpandProperty modules -first 1 | select -property * -first 1

#As you can see, we could keep going deeper and deeper.  Some commands produce complicated objects like this.  Others provide simpler objects.  Get comfortable using Get-Member, Select-Object and Format-List to explore the properties and methods for the objects you are working with.

Let’s use some of these properties that aren’t displayed by default, and a method.  We will open Notepad, find the most recent process opened and close it.

notepad.exe
$latestProcess = get-process | sort starttime -Descending | select -first 1

#Display the process we picked, show the name, ID and startTime.  We can show or work with any of the properties we found with Get-Member
$latestProcess | select ProcessName, ID, StartTime

#If you are satisfied with the process you picked, close it using the close method.
$latestProcess.close()

The concept of objects and the complexities of working with them can be difficult when starting out.  You don’t need to memorize the different types of objects and all the properties and methods; just know that they are there, you won’t always see them by default, and that it is easy to dive into an object to see what it contains.  Things get even more interesting when you realize you have access to the .NET Framework – MSDN is your friend.

That’s it for today! Stop by PowerShell Resources for a list of PowerShell Resources that I’ve found helpful in learning PowerShell and for day to day use.