Article Contents:
In the future at work my supervisor requested me to seek out all providers working below completely different credentials on a handful of a particular servers. The ask went one thing like this:
“Hey Mike, can you discover all of the providers configured to run as one other person on a particular group of servers?”
My supervisor hadn’t even completed his sentence, and I had already thought in my head, “No downside! I am going to crank that out in PowerShell in a mere couple of minutes!” The ask was benign, however as I quickly came upon, the method for locating the requested info was not as easy as I assumed. Let’s dive in and take a look at how I solved this request.
First, let’s evaluate the duty and break it down into related items:
“Discover all of the providers configured to run as one other person on a gaggle of servers.”
From that sentence, we will break that down to those separate duties to unravel:
- question a gaggle of servers
- discover all of the providers configured to run as one other person
- return the outcomes
- (implied however not stated) format the output of the outcomes in order that we will perceive the knowledge
Discovering Logon As or Run As info
The very first thing we’ll want to determine is a strategy to discover the “logon as” properties for providers on Home windows pc. Let’s begin by querying a service on my native pc to see what fields can be found. I am going to begin with a easy assist lookup:
PS C:Usersmkana> assist get-service
NAME
Get-Service
SYNOPSIS
Will get the providers on a neighborhood or distant pc.
SYNTAX
Get-Service [-ComputerName <String[]>] [-DependentServices] -DisplayName <String[]> [-Exclude <String[]>]
[-Include <String[]>] [-RequiredServices [<CommonParameters>]
Get-Service [-ComputerName <String[]>] [-DependentServices] [-Exclude <String[]>] [-Include <String[]>]
[-InputObject <ServiceController[]>] [-RequiredServices] [<CommonParameters>]
Get-Service [[-Name] <String[]>] [-ComputerName <String[]>] [-DependentServices] [-Exclude <String[]>]
[-Include <String[]>] [-RequiredServices] [<CommonParameters>]
I see there are ComputerName
and DisplayName
fields which might be useful, however nothing that offers me ‘Logon As‘ or ‘Run As‘.
Let’s attempt piping get-service
to get-member
and see if there are extra properties out there to me. I can take a look at the MemberType column and see all of the properties out there.
PS C:Usersmkana> get-service LanmanServer | get-member
TypeName: System.ServiceProcess.ServiceController
Identify MemberType Definition
---- ---------- ----------
Identify AliasProperty Identify = ServiceName
RequiredServices AliasProperty RequiredServices = ServicesDependedOn
Disposed Occasion System.EventHandler Disposed(System.Object, System.EventArgs)
Shut Technique void Shut()
Proceed Technique void Proceed()
CreateObjRef Technique System.Runtime.Remoting.ObjRef CreateObjRef(sort requestedType)
Dispose Technique void Dispose(), void IDisposable.Dispose()
Equals Technique bool Equals(System.Object obj)
ExecuteCommand Technique void ExecuteCommand(int command)
GetHashCode Technique int GetHashCode()
GetLifetimeService Technique System.Object GetLifetimeService()
GetType Technique sort GetType()
InitializeLifetimeService Technique System.Object InitializeLifetimeService()
Pause Technique void Pause()
Refresh Technique void Refresh()
Begin Technique void Begin(), void Begin(string[] args)
Cease Technique void Cease()
WaitForStatus Technique void WaitForStatus(System.ServiceProcess.ServiceControllerStatus desiredStatus), void WaitForStat
CanPauseAndContinue Property bool CanPauseAndContinue {get;}
CanShutdown Property bool CanShutdown {get;}
CanStop Property bool CanStop {get;}
Container Property System.ComponentModel.IContainer Container {get;}
DependentServices Property System.ServiceProcess.ServiceController[] DependentServices {get;}
DisplayName Property string DisplayName {get;set;}
MachineName Property string MachineName {get;set;}
ServiceHandle Property System.Runtime.InteropServices.SafeHandle ServiceHandle {get;}
ServiceName Property string ServiceName {get;set;}
ServicesDependedOn Property System.ServiceProcess.ServiceController[] ServicesDependedOn {get;}
ServiceType Property System.ServiceProcess.ServiceType ServiceType {get;}
Website Property System.ComponentModel.ISite Website {get;set;}
StartType Property System.ServiceProcess.ServiceStartMode StartType {get;}
Standing Property System.ServiceProcess.ServiceControllerStatus Standing {get;}
ToString ScriptMethod System.Object ToString();
As I look by means of the record, I’m on the lookout for properties that could be useful or a match. Nevertheless, I see nothing that can return the ‘Logon As‘ or ‘Run_As‘ info that I would like; that is an issue. I can use Get-Service
to question the service info of a neighborhood or distant pc, however that cmdlet doesn’t return any ‘Logon As’ or ‘Run_As‘ info in any respect.
Here is an instance:
PS C:Usersmkana> get-service LanmanServer | Choose-Object *
Identify : LanmanServer
RequiredServices : {SamSS, Srv2}
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
DisplayName : Server
DependentServices : {}
MachineName : .
ServiceName : LanmanServer
ServicesDependedOn : {SamSS, Srv2}
ServiceHandle :
Standing : Working
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Computerized
Website :
Container :
Houston, we now have an issue!
Looking out CIM Knowledge for properties
We’ll have to think about using another technique of retrieving the related info in an effort to full this request. Let’s examine what’s out there from WMI/CIM.
A be aware about CIM and WMI
💡
Get-CIMInstance
cmdlet as a substitute of Get-WMIObject
. It is the newer, extra fashionable model of WMI however makes use of PS remoting (aka WinRM) and falls again to WMI typically if CIM fails.
It is like one of the best of each worlds! Your community workforce will love CIM over WMI as a result of it makes use of commonplace ports as a substitute of a variety of ports like WMI makes use of! If you have not already transitioned to utilizing Get-CIMInstance
when querying WMI, then you have to use Get-CIMInstance
as your default technique, as a substitute of Get-WMIObject
.
Get-WMIObject` does not work on newer variations of Home windows (Home windows 10, 11, Server2016, Server2019 & Server 2022)
I talk about the variations between CIM and WMI at size in my article on The right way to safe PowerShell Remoting in a Home windows Area .
The lookup I have to carry out for CIM is:
PS > Get-CIMInstance -Class Win32_Service -Filter "title="LanmanServer" " | Choose-Object *
I am going to clarify this syntax in only a second. However first, let’s take a look at the outcomes. I’m displaying you a display screen cap as a result of it is simpler to spotlight the knowledge I’m on the lookout for.
Alright, now we’re getting someplace!
CIM offers us what we’d like, now we simply have to determine what fields we need to return then do some filtering. First, we have to resolve what data we need to return from CIM. I feel the next fields cowl the whole lot we’d like for this activity:
- SystemName
- Identify
- Caption
- StartMode
- StartName
- State
Your mileage could differ and you’ll select no matter fields you like, however for this downside, these fields fill the invoice.
Constructing a question with Get-CIMInstance
Now that we all know what we need to question, we have to work on some filters. Let’s question the fields we simply recognized and make these properties comprise the info we count on. Here is a fast check to see how that appears for one service question:
PS > Get-CIMInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " |
Choose-Object SystemName, Identify, Caption, StartMode, StartName, State | Kind-Object StartName
Systemname : DC02
Identify : tapisrv
Caption : Telephony
StartMode : Handbook
StartName : mkmkadmin
State : Stopped
That appears like the whole lot I would like for my authentic ask. Let’s evaluate the place we’re at thus far.
We have now queried the knowledge we’d like, and we now have discovered the properties to question. Now, let’s determine the way to seek for providers configured to run as one other person. If we take a look at a typical PC, the overwhelming majority of providers run as one among three built-in accounts:
- LocalSystem
- NT AUTHORITYLocalService
- NT AUTHORITYNetworkService
I can construct a filter to return accounts not configured as one of many three accounts listed above. Since we’re on the lookout for ANY account used to RUN AS, any account besides these three above can be legitimate outcomes.
To look CIM (or WMI) for any account besides the three above, we’d use the filter parameter and the syntax would appear like this:
-filter " StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' "
The !=
is equal to NOT EQUAL TO and the assertion AND NOT StartName LIKE
is similar as "StartName -notlike 'NT Authority%'"
in conventional PowerShell code. We will interpret the search as “Discover all accounts which aren’t equal to the title LocalSystem and likewise don’t begin with NT Authority* “. In less complicated phrases, it interprets to “all accounts that aren’t one among three inbuilt we listed above”.
Let’s check that question to ensure it really works as we count on. So as to take action, I reconfigured the Telephony Service to run below my admin credentials.
Let’s examine if our filter works as anticipated. If it does, we must always see the one service (Telephony Service) returned as the one service configured with an alternate Logon As credential.
Get-CIMInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " |
Choose-Object SystemName, Identify, Caption, StartMode, StartName, State | Kind-Object StartName
SystemName : DC02
Identify : tapisrv
Caption : Telephony
StartMode : Handbook
StartName : mkmkadmin
State : Stopped
Success!!!
Now that we now have a working search standards, we will run this question in opposition to the servers. We must always get an inventory of providers that match our standards of working with an alternate Logon As credential.
I’ve three servers I’m going to question: DC01, DC02, and AZBuild01. There are a number of strategies that may join to every server and carry out the question. I might use ForEach-Object
and join utilizing Get-CIMInstance
and add the -ComputerName
parameter. For this activity, I desire to make use of PowerShell remoting (aka WinRM).
Utilizing PowerShell Remoting to connect with computer systems
The syntax for PowerShell Remoting is useless easy to make use of. The cmdlet we’ll use is Invoke-Command
(or ICM alias). We might have used Enter-PSSession
to connect with every server interactively. As a substitute, Invoke-Command
will hook up with as much as 32 computer systems in a single shot within the background. It’s going to retrieve the knowledge I ask for and return the info to me in a formatted output.
💡
The syntax is the cmdlet title, the pc title after which the script block. The script block is the place you place the precise lookup you wish to have carried out on every pc.
Invoke-Command -ComputerName MyServerNames -ScriptBlock {Insert your code right here}
Should you recall out earlier, lookup through CIMInstance was:
Get-CIMInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " |
choose systemname, title, caption, startmode, startname, State | kind startname
the syntax utilizing Invoke-Command
can be:
Invoke-Command "DC01", "DC02", "AzBuild01" -Scriptblock kind startname
The output we get is:
Outcomes return rapidly as a result of we’re utilizing -filter
as a substitute of where-object
. The distinction is that filter sends the code to the distant pc. The pc does the lookup and finds the matches and solely sends again the matches. If I used where-object
, then all of the providers from the distant machine can be despatched again after which after all of the computer systems have responded with all their providers, where-object would then filter out the outcomes on my native pc.
I’m solely contacting three computer systems, so the distinction in pace is just not noticeable. But when I despatched this to 100 or 200 computer systems, then the distinction between utilizing -filter
or The place-object
turns into a giant deal. The distinction is often seconds vs ten’s of seconds for The place-object. You additionally need to use -filter
as a substitute of where-object
until -filter is just not an possibility that’s out there for a cmdlet.
Formatting output
We have now fulfilled the request, however I see two issues. The primary downside is that the output has some fields that my supervisor is not going to know what they imply. The fields Identify and Caption are ambiguous and want higher clarification. We will repair through the use of expressions to customise the names.
Invoke-Command "DC01", "DC02", "AzBuild01" -Scriptblock {
Get-CIMInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " |
Choose-Object SystemName, @{ Identify="ServiceName"; Expression = {$_.Identify}},
@{ Identify="Service DisplayName"; Expression = {$_.Caption}}, StartMode, StartName, State | kind StartName}
That is higher. Now once I ship the outcomes to my supervisor, he’ll perceive the output and never should ask me questions.
Simplifying our code
The second downside I see in my code is that it is now a monster command that’s exhausting for others to grasp. If I save my code and go it to a junior admin or a PowerShell novice, they might wrestle making an attempt to grasp the code. We solved the issue, however we must always attempt to enhance the readability for others. The answer for readability is variables.
If we take the assorted components of the code and save them to variables, then the cmdlet will get a lot simpler to learn. We will assign variables for the server names and the 2 expressions.
Let’s begin with the server names:
$Servers = "DC01", "DC02", "AzBuild01"
and we will additionally do the identical for expressions:
$ServiceName = @{ Identify="ServiceName"; Expression = {$_.Identify}}
$ServiceDisplayname = @{ Identify="Service DisplayName"; Expression = {$_.Caption}}
Now we will substitute these values in our code:
$Servers = "DC01", "DC02", "AzBuild01"
$ServiceName = @{ Identify="ServiceName"; Expression = {$_.Identify}}
$ServiceDisplayname = @{ Identify="Service DisplayName"; Expression = {$_.Caption}}
Invoke-Command $servers -ScriptBlock {
Get-CimInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " } |
Choose-Object SystemName, $ServiceName, $ServiceDisplayname, StartMode, StartName, State
)
Our code is far more readable than earlier and once we hand it over to another person, they’ll have a significantly better probability of understanding what this code does.
The final change I’ll make is to alter the output to record as a desk.
$Servers = "DC01", "DC02", "AzBuild01"
$ServiceName = @{ Identify="ServiceName"; Expression = {$_.Identify}}
$ServiceDisplayname = @{ Identify="Service DisplayName"; Expression = {$_.Caption}}
Invoke-Command $servers -ScriptBlock {
Get-CimInstance -Class Win32_Service -filter "StartName != 'LocalSystem' AND NOT StartName LIKE 'NT Authority%' " } |
Choose-Object SystemName, $ServiceName, $ServiceDisplayname, StartMode, StartName, State | format-table -autosize
Conclusion
I hoped you loved this look into how I tackled a real-world downside. We began with a activity and located the pertinent info wanted to meet the request. After we discovered what and the way to question the knowledge, we labored on the way to repeat that question in opposition to a number of computer systems. Then we labored on formatting the output to one thing that’s human readable for non admin workers to work with. We might nonetheless take these outcomes and export them to a CSV or higher but, an Excel file, that are commonplace workplace format nearly anybody can open and eat.
Thanks for studying, I might like to know what you suppose. Go away me a message within the remark part on the backside of the web page.