Hey, Scripting Guy! Question

Hey, Scripting Guy! I have several computers that seem to be identical, but they act like they are different. I mean, the same person set the computers up, so I imagine he followed the checklist and the machines should therefore be the same. But something weird is going on. What I need is a script that will tell me which services are defined on a local computer along with a list of the users and groups that have been created. I have pretty well narrowed things down to these three variables. Can you help me?

- RI

SpacerHey, Scripting Guy! Answer

Hi RI,

I suppose you think you are going to get me sidetracked with a WMI question during the last day of our Local Account Management Week, don’t you? Well, no such luck. Interestingly enough, we could write this entire script by using WMI because WMI will give us a list of local users, groups, and services. But while you may expect ADSI to be able to provide information about users and groups, did you know we can obtain service information as well? This is all documented in the MSDN article mentioned below.

This week we will be looking at scripting Windows PowerShell as it applies to local account management. This is an area that comes up from time to time and for which there are not an awful lot of resources from which to choose. We have these tasks in the Script Center Script Repository pretty well hidden away in the Other Directory Services category. There are some great scripts in the Community-Submitted Scripts Center. Local account management has been a favorite topic of the “Hey, Scripting Guy!” articles over the years, and as a result we have a good selection of articles grouped together in the “Hey, Scripting Guy!” archive. The most extensive reference you will find is the MSDN coverage of the WinNT ADSI provider.

We have decided to write a script called WorkWithLocalWinNTObjects.ps1. It will display all the users, groups, and services that are defined on your local computer. We do not have an example of a VBScript that does this same thing.

Function funLine($strIN)
{
  $strOUT = "-" * $strIN.length
  "$strin`n$strOUT"
}

$domainName = "nwtraders"
$computerName = "vista"
$computer = [adsi]"WinNT://$domainName/$computerName"
$objCount = ($computer.psbase.children | measure-object).count
$i=0
foreach($adsiObj in $computer.psbase.children)
{
 write-progress -activity "getting objects" -status "progress" -percentComplete ($i / $objCount*100)
  switch -regex($adsiObj.psbase.SchemaClassName)
    {
       "group" { $group += $adsiObj.name }
       "user"    { $user += $adsiObj.name }
       "service" { $service +=  $adsiObj.name }

   } #end switch
  $i++
} #end foreach

funline("groups on $domainName\$computerName") ; $group ; "`r" 

funline("users on $domainName\$computerName") ; $user ; "`r"

funline("services on $domainName\$computerName"); $service; "`r"

The first thing we do in the WorkWithLocalWinNTObjects.ps1 script is create a function that we call funLine. The function receives a string for an input parameter and we therefore decide to call the input variable $strIN. This is seen here:

Function funLine($strIN)
{

The funLine function measures the length of the input word and creates an underline character that is the same length as the word. We will use funLine to underline the section parts of our report. To create the underline, we use the length property of the input string and then we multiply the character by the length of the input string. This creates a line that is exactly as long as the input string. This is seen here:

PS C:\> "-" * $String.length
----------------

We store the underline in the $strOUT variable for use later in the function:

$strOUT = "-" * $strIN.length

Now we create a string that contains the input string, a new line character, and the underline. This new string will be returned from the function when it is called:

"$strin`n$strOUT"
}

When you think of using the WinNT ADSI provider to work with local computer accounts, you may not think about working with the domain name as part of the occasion. Because it might be possible for two computers to have the same workstation name but to be separated by having different domain names, we decide to include the domain name as part of the computer name that is passed to the WinNT provider. We store the domain name in the $domainName variable:

$domainName = "nwtraders"

The computer name is stored in the $computerName variable:

$computerName = "lisbon”

After we have both the domain name and the computer name stored in the appropriate variables, we use the [adsi] type accelerator and the WinNT ADSI provider to connect to the computer designated by the domain name value and the computer name value. The returned computer object is stored in the $computer variable. This is all seen here:

$computer = [adsi]"WinNT://$domainName/$computerName"

After we make the connection to the computer, we use the base children property to obtain a collection of all the child objects defined on the computer. We pipeline the child objects to the Measure-Object cmdlet and retrieve only the count property. We store that value in the $objcount variable:

$objCount = ($computer.psbase.children | measure-object).count

Then we need to initialize a counter variable. We set the value of $i to zero:

$i=0

Now we need to walk through the collection of child objects. To do this, we use the ForEach statement as seen here:

foreach($adsiObj in $computer.psbase.children)
{

Next we use the Write-Progress cmdlet to display a status update bar on the screen. We use three of the parameters of the Write-Progress cmdlet: the activity that names the activity being performed; the status parameter, which notifies the user of the status of the activity; and the percent complete parameter, which governs the behavior of the status line that is seen here:

Image of the percent complete parameter in action

 

The line of code that produces the progress indicator screen is seen here:

write-progress -activity "obtaining objects" -status "progress" -percentComplete ($i / $objCount*100)

Now we need to gather information for our report. To do this, we use the switch statement with a simple regular expression pattern match. We use the SchemaClassName attribute to tell us if an object is a user, group, or service. The beginning of our switch statement is seen here. The switch statement is documented in this Windows PowerShell tip on the Script Center.

switch -regex($adsiObj.psbase.SchemaClassName)
    {

The first thing we look for are groups. If the SchemaClassName matches the word “group”, we concatenate the name of the object with the contents of the variable $group:

"group" { $group += $adsiObj.name }

If the SchemaClassName matches the word “user”, we concatenate the name of the object with the contents of the variable $user:

"user"    { $user += $adsiObj.name }

After we have collected our user names and our group names, we look at the remaining objects to see if the SchemaClassName matches the word “service”. If it does, we concatenate the name of the service with the other service names contained in the $service variable. This code is shown here:

"service" { $service +=  $adsiObj.name }
   } #end switch

Next we increment the $i counter variable and continue with the ForEach statement until all the objects have been processed. This is seen here:

$i++
} #end foreach

Now it is time for our report. We use the funLine function to underline the string that is fed to the $strIN parameter. We then print out the data contained in the variables and include a carriage return at the end of each line:

funline("groups on $domainName\$computerName") ; $group ; "`r" 
funline("users on $domainName\$computerName") ; $user ; "`r"
funline("services on $domainName\$computerName"); $service; "`r"

When we run the script we are treated with this report:

Image of the report generated by the script

 

Well, RI, we were able to report on our local users, groups, and services without having to write any WMI code. Along the way, we looked at a couple of my favorite Windows PowerShell cmdlets: Measure-Object and Write-Progress. Both of these cmdlets are helpful from time to time and both are easy to use. This also concludes our series of articles concerning working with local account management. We hope you have found the series helpful, and informative. Join us tomorrow for Quick-Hits Friday where we will explore a variety of topics related to both Windows PowerShell and VBScript. See you tomorrow.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys