Bookmark and Share


(Note: These solutions were written for Beginner Event 6 of the 2010 Scripting Games.) 
 

Beginner Event 6 (Windows PowerShell)

Photo of Jeffery Hicks


Jeffery Hicks is a Microsoft MVP in Windows PowerShell and an IT veteran with almost 20 years of experience, much of it spent as an IT consultant specializing in Windows server technologies. He works today as an independent author, trainer, and consultant. He is also known for his Prof. PowerShell column on MCPMag.com. Jeff’s latest book is Windows PowerShell 2.0: TFM (SAPIEN Press 2010). He is a moderator at ScriptingAnswers.com, a subject matter expert at TheExpertsCommunity.com, and a frequent contributor to many scripting-related forums. You can follow what Jeff is up to at jdhitsolutions.com/blog and twitter.com/jeffhicks.


------------


The most challenging aspect of this event was to read values from a remote registry. Unfortunately, there aren’t any registry-related cmdlets. It is possible to use WMI or the .NET Framework to retrieve remote registry values, but the WMI approach is too cumbersome and slow performing and using the .NET Framework classes requires a higher level of Windows PowerShell experience. However, using the registry PSDrive to retrieve the values is relatively easy. The “trick” to this challenge is to use Windows PowerShell remoting so that I can access the HKLM: PSDrive on each remote machine.


I knew I could use the Invoke-Command cmdlet to run a Windows PowerShell expression on a remote computer. I also discovered through reading documentation that in order to use an IP address as the computername, I needed to include credentials and either configure WinRM on the remote machines to use HTTPS or add the IP addresses as trusted hosts. I used Group Policy to properly configure my network. Needless to say, this solution requires Windows PowerShell 2.0 on all machines with a properly configured WinRM service.


I wrote a scriptblock using Get-ItemProperty to retrieve the required registry values. Because I knew I had to read a couple of values I wrote a small function. This way I only had to write code once, and I could add error handling in case the registry value didn’t exist.


The service properties required WMI because the Get-Service cmdlet returns a .NET Framework version of the service object, which doesn’t include the start mode. Thus, I used Get-WmiObject to query the browser service.


Whenever I write Windows PowerShell, I’m always thinking “objects.” If I write an object to the pipeline I have much more flexibility. So my scriptblock ends by creating a new object, passing it a hash table of properties.


I tested the scriptblock in an elevated Windows PowerShell session. After copying and pasting the scriptblock, I ran it locally:


PS C:\Scripts> Invoke-Command -ScriptBlock $scriptblock

IsDomainMaster     : False

BrowserStart       : Manual

MaintainServerList : False

Computername       : SERENITY

BrowserStatus      : Stopped



Now that I know this works, all that remains is to run through the range of IP addresses. Windows PowerShell will treat any number range as an array. That means I can pipe the range to ForEach and construct an IP address. I also included a Write-Host expression to let the administrator know what is happening. This is shown here:


1..254 | foreach {

 $ip="192.168.1.$_"

 Write-Host "Scanning $ip" -ForegroundColor Cyan



The Invoke-Command expression is executed using the scriptblock and a saved administrator credential, which is required when using IP addresses. In this particular scenario, the IP address is considered the computername. Invoke-Command includes a special property called PSComputername, which retains this value. This is very useful when running a command on multiple computers.


Because of the way this property is handled by Windows PowerShell, I used Format-List to select the properties I needed and a custom label so that PSComputername is displayed as IPAddress. I could have simply piped the results of Format-Table to Out-File, but I thought it might be nice to also see the results as they are returned, so I used Tee-Object. This writes objects to the pipeline and saves them to a file.


The following image shows the script in action.

Image of script in action


Here is the script:

#requires -version 2.0

$scriptblock={

#define a variable to reuse and save typing

$RegPath="HKLM:System\CurrentControlSet\Services\Browser\Parameters"

#this function can be reused as needed to return

#registry values

Function Get-RegistryValue {

 Param ([string]$RegPath,[string]$Name)

 #turn off the error pipeline for this function

 $errorActionPreference="SilentlyContinue"

 $data=Get-Itemproperty -Path $Regpath -Name $name

 #the registry item might not exist so check first

 if ($data) {

   #write the registry value to the pipeline

   Write-Output $data.$name

   }

   else {

   write-output "NotFound"

   }

} #end Get-RegistryValue

 #get the registry values

$maintain=Get-RegistryValue -regPath $RegPath -name "MaintainServerList"

$IsDomain=Get-RegistryValue -regPath $RegPath -name "IsDomainMaster"

#use WMI to get the browser service

$browser=Get-WmiObject -Class Win32_Service -filter "Name='browser'"

#write a custom object to the pipeline specifying a hash table

#of properties

New-Object PSObject -Property @{

 Computername=$env:computername

 MaintainServerList=$maintain

 IsDomainMaster=$IsDomain

 BrowserStatus=$browser.State

 BrowserStart=$browser.StartMode

 }

} #end Scriptblock

#get domain admin credential required when using an IP address

#with Invoke-Command

$admin=Get-Credential "$env:userdomain\administrator"

#the name of the final text report

$file=".\Report.txt"

#The range 1..254 will write numbers 1 through 254 to the pipeline

1..254 | foreach {

 $ip="192.168.1.$_"

 Write-Host "Scanning $ip" -ForegroundColor Cyan

 Invoke-Command -ComputerName $ip -ScriptBlock $scriptblock -Credential $admin

 } | Format-List @{Label="IPAddress";Expression={$_.PSComputername}},`

 Computername,MaintainServerList,IsDomainMaster,Browser* |

 Tee-Object -FilePath $file

 #let the admin know the script if finished.

 write-host "Scan finished. Open $file to see results." -ForegroundColor Green

 


Beginner Event 6 (VBScript)

Image of Uros Calakovic


Uros Calakovic, System and Database Administrator
Bijeljina, Bosnia And Herzegovina
My Code Project articles: http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=4210975


----------------


Before starting to write the script, it is a good idea to find more information about the problem you are trying to solve. If you are an average system administrator like me, you probably have an idea about what the browser service does, but a quick search reveals more details: the computer browser service lets users browse neighboring computers and their shared resources, the information you see when you open My Network Places. On each network segment, a computer running the browser service is elected to play the role of the master browser.It maintains a list of the available computer names and their shared resources.


In the Event 6 scenario, browser elections are being forced on a network subnet every 15 minutes, causing unnecessary network traffic. The situation can be resolved by setting the IsDomainMaster registry value to False, and setting the MaintainServerList value to No in this registry key:


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Browser\Parameters



The task is to inspect every computer on the subnet and make a report that contains:

·         Computer IP address.

·         Computer name.

·         MaintainServerList registry value.

·         IsDomainMaster registry value.

·         Browser Service status.

·         Browser Service start mode.


After getting the requirements, the steps for creating the script look like this:


1.    
Create and open a text file into which information will be written.

2.     Connect to every computer in the given range and for each:

a.     Get the computer name.

b.    Get the browser service status and start mode.

c.     Get MaintainServerList and IsDomainMaster registry values.

d.    Write the information to the file.

3.     After visiting all computers, close the file.


The first step is to create and open a file into which the gathered information will be written. In this case, a CSV file is probably the best choice. You can easily open it with Excel and make a nice report. Here is what the code for this step looks like:


strFileName = "C:\Scripts\BrowserServiceLog.csv"

blnOverwrite = True

Set objFso = CreateObject("Scripting.FileSystemObject")

Set objLogFile = objFso.CreateTextFile(strFileName, blnOverwrite)

' The rest of the code goes here

objLogFile.Close



The code creates an instance of the FileSystemObject object and uses its CreateTextFile method to create a text file with the .csv extension. The Overwrite argument is set to True. While testing the script, the CSV file will be created several times, which lets the existing one be overwritten silently.


The second step is to ping all computers with IP addresses ranging from 192.168.1.1 to 192.168.1.24. The easiest way to do this is to create a string variable that contains the first three octets of the target IP addresses ( '192.168.1.') and append strings from 1 to 24 to it in a loop, to get the IP address of each computer in the network segment. The IP address is then used to ping the remote computer. If the computer is available, the script gets the data and writes it to the log file; otherwise, it writes an error message to the log file:


strSegment = "192.168.1."

For i = 1 To 24

    strIPAddress = strSegment & CStr(i)

 

    If IsConnectible(strIPAddress) Then       

        objLogFile.WriteLine GetCSVLine(strSegment & CStr(i))

    Else

        objLogFile.WriteLine _

            strIPAddress & ": Not available."

    End If

 

Next



The function named IsConnectible pings a remote computer to check if it is available. Richard Mueller has four different versions of this function listed here, and you can use one of them, depending on your operating system and Windows Script Host version. (I used the fourth version that uses the Ping command internally and modified it a bit.) Checking the remote computer availability can be skipped, but it lets you avoid the timeout that occurs if the computer is not available, saving a little time.


The second function, GetCVSLine, will contain the code that gets the necessary data and returns it as a line of comma-separated values ready to be written to the log file.


The script needs to collect five pieces of information from each computer in the segment: two registry values, the state and start mode of a Windows service, and the remote machine name. All the data can be obtained using WMI. Here is the GetCSVLine function:


Function GetCSVLine(strIPAddress)

  

    GetBrowserServiceData strIPAddress, _

        strState, strStartMode, strServerName

   

    strMaintainServerList = GetStringRegistryValue _

        (strIPAddress, HKLM, strRegKey, "MaintainServerList")

       

    strIsDomainMaster = GetStringRegistryValue _

        (strIPAddress, HKLM, strRegKey, "IsDomainMaster")

   

    GetCSVLine = strIPAddress & "," & strServerName & "," _

        & "," & strState & "," & strStartMode _

        & strMaintainServerList & "," & strIsDomainMaster

   

End Function



GetCSVLine
itself calls two functions to get the data (GetBrowserServiceData and GetStringRegistryValue), and returns a string of comma-separated values.


In step 3, you need to get the browser service state and start mode. These values are available from the Win32_Service WMI class. In step 4, you need to get the remote computer name, which can be obtained using the Win32_ComputerSystem WMI class, but is also available from the __Server property of each WMI class on that computer. This is handy because you can get all three values using one WMI call, saving a bit of time. GetBrowserServiceData is used to get all three pieces of data. GetBrowserServiceData accepts accept an IP address as the input parameter (strIPAddress), and has three output values (strState, strStartMode, and strServerName). In step 5, the script needs to read two string registry values (MaintainServerList and IsDomainMaster). The function named GetStringRegistryValue is used for this.


Here is the GetBrowserServiceData code:


Sub GetBrowserServiceData(ByVal strComputer, _

    ByRef strState, ByRef strStartMode, ByRef strServerName)

 

    On Error Resume Next

   

    Set objBrowserService = GetObject("winmgmts:" _

        & "{impersonationLevel=impersonate}!\\" & strComputer _

        & "\root\cimv2:Win32_Service.Name='Browser'")

   

    If Err.number = 0 Then 

        strState = objBrowserService.State

        strStartMode = objBrowserService.StartMode

        strServerName = objBrowserService.Path_.Server 

    Else

        strState = "Error getting service state"

        strStartMode = "Error getting service start mode"

        strServerName = "Error getting computer name"

    End If

   

    Err.Clear   

    On Error Goto 0

   

End Sub



The subroutine first attempts to bind to the remote Win32_Service instance, whose Name property value is Browser. The Name is the Win32_Service key property, and this allows you to use it to bind directly to a Win32_Service instance. This way you avoid using a WMI query and enumerating the returned instances. If this is successful, the subroutine gets the service state, start mode, and computer name values. If not, all three return values are set to indicate an error.


Here is the GetStringRegistryValue function:


Function GetStringRegistryValue(ByVal strComputer, _

    ByVal intHive, ByVal strKeyName, ByVal strValueName)

   

    On Error Resume Next

   

    Set objStdRegProv = GetObject _

        ("winmgmts:" & "{impersonationLevel=impersonate}!\\" _

        & strComputer & "\root\default:StdRegProv")

   

    If Err.Number = 0 Then

        intRetVal = objStdRegProv.GetStringValue _

            (intHive, strKeyName, strValueName, GetStringRegistryValue)

       

        If intRetVal <> 0 Then

            GetStringRegistryValue = "Error reading registry"

        End If

    Else

        GetStringRegistryValue = "Error connecting to remote registry"

    End If

   

    Err.Clear   

    On Error Goto 0

 

End Function   


The function makes use of the StdRegProv WMI class, which is commonly used for reading registry data remotely. The StdRegProv class is located in the Root\Default WMI namespace. It has no instances and no properties, but it has 16 methods for manipulating the registry data. Just like in the first case, the function attempts to bind to StdRegProv, and if this is successful, it uses the StdRegProv GetStringValue method to read the value from the remote registry. If this is not successful, the function return value is set to an error message.


The GetStringValue method returns an integer value that indicates if the call was successful, so this return value also needs to be checked. (You can find out more about WMI error handling here).


I tested the script using four Microsoft Virtual PC 2007 virtual machines and here is what the log file looks like in Excel:

Image of log file in Excel

 

The script fails to read the registry values from the third computer; only four computers in the given IP range are accessible.

 

Here is the full script.

' -------------------------------------------------------------------

'

' Author: Uros Calakovic,  2010-3-26

' Name: Beg_6_Solution.vbs

'

' VBScript solution for Beginner Event 6

'

' The script reads two registry values and

' Windows service information from a range of computers

'

' -------------------------------------------------------------------

 

Option Explicit

 

' Declarations

 

Dim objFso, objLogFile

Dim strFileName, blnOverwrite

 

Dim strIPAddress, strSegment

Dim strRegKey

Dim i

 

Const HKLM = &H80000002

 

strSegment = "192.168.1."

strRegKey = "SYSTEM\\CurrentControlSet\\services\\Browser\\Parameters"

 

strFileName = "C:\Scripts\BrowserServiceLog.csv"

blnOverwrite = True

 

' Create the log file and write the header line

 

Set objFso = CreateObject("Scripting.FileSystemObject")

Set objLogFile = objFso.CreateTextFile(strFileName, blnOverwrite)

objLogFile.WriteLine _

    "IP Address,Computer Name,Browser Status," _

    & "Browser Start Mode,MaintainServerList," _

    & "IsDomainMaster"

 

' Loop through IP addresses and get the data

 

For i = 1 To 24

 

    strIPAddress = strSegment & CStr(i)

   

    If IsConnectible(strIPAddress) Then       

        objLogFile.WriteLine GetCSVLine(strSegment & CStr(i))

    Else

        objLogFile.WriteLine _

            strIPAddress & ": Not available."

    End If

 

Next

 

objLogFile.Close

 

' ---------------------------------------------------------

 

Function GetCSVLine(strIPAddress)

 

    Dim strState, strStartMode, strServerName

    Dim strMaintainServerList, strIsDomainMaster

   

    GetBrowserServiceData strIPAddress, _

        strState, strStartMode, strServerName

   

    strMaintainServerList = GetStringRegistryValue _

        (strIPAddress, HKLM, strRegKey, "MaintainServerList")

       

    strIsDomainMaster = GetStringRegistryValue _

        (strIPAddress, HKLM, strRegKey, "IsDomainMaster")

   

    GetCSVLine = strIPAddress & "," & strServerName & "," _

        & strState & "," & strStartMode & "," _

        & strMaintainServerList & "," & strIsDomainMaster

   

End Function

 

' ---------------------------------------------------------

 

Sub GetBrowserServiceData(ByVal strComputer, _

    ByRef strState, ByRef strStartMode, ByRef strServerName)

   

    ' Input:

    '     strComputer - remote computer name or IP address

    ' Output:

    '    strState - Browser service state string

    '    strStartMode - Browser service start mode

    '    strServerName - Remote computer name

   

    Dim objWmi, objBrowserService

   

    On Error Resume Next

   

    Set objBrowserService = GetObject("winmgmts:" _

        & "{impersonationLevel=impersonate}!\\" & strComputer _

        & "\root\cimv2:Win32_Service.Name='Browser'")

   

    If Err.number = 0 Then   

        strState = objBrowserService.State

        strStartMode = objBrowserService.StartMode

        strServerName = objBrowserService.Path_.Server

    Else   

        strState = "Error getting service state"

        strStartMode = "Error getting service start mode"

        strServerName = "Error getting computer name"

    End If

   

    Err.Clear   

    On Error Goto 0

   

End Sub

 

' ---------------------------------------------------------

 

Function GetStringRegistryValue(ByVal strComputer, _

    ByVal intHive, ByVal strKeyName, ByVal strValueName)

   

    ' Input:

    '    strComputer - remote computer name or IP address

    '    intHive - registry hive

    '    strKeyName - registry key containing the value

    '    strValueName - registry value name to be read

    ' Return value:

    '    string registry value

   

    Dim objWmi, objStdRegProv

    Dim intRetVal

   

    On Error Resume Next

   

    Set objStdRegProv = GetObject _

        ("winmgmts:" & "{impersonationLevel=impersonate}!\\" _

        & strComputer & "\root\default:StdRegProv")

   

    If Err.Number = 0 Then

        intRetVal = objStdRegProv.GetStringValue _

            (intHive, strKeyName, strValueName, GetStringRegistryValue)

       

        If intRetVal <> 0 Then

            GetStringRegistryValue = "Error reading registry"

        End If

    Else

        GetStringRegistryValue = "Error connecting to remote registry"

    End If

   

    Err.Clear   

    On Error Goto 0

 

End Function

 

' ---------------------------------------------------------

 

Function IsConnectible(ByVal strHost)

 

    ' Input:

    '    strHost - remote computer name or IP address

    ' Return value:

    '   True if strHost can be pinged, False if not

   

    Dim objShell, lngResult, intPings, intTO

   

    Set objShell = CreateObject("WScript.Shell")

   

    ' ping count

    intPings = 2

   

    ' timeout in milliseconds

    intTO = 500

 

    lngResult = objShell.Run("%comspec% /c ping -n " & intPings _

        & " -w " & intTO & " " & strHost _

        & " | find ""TTL="" > nul 2>&1", 0, True)

 

    Select Case lngResult

        Case 0

            IsConnectible = True

        Case Else

            IsConnectible = False

    End Select

 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys