Using Custom Objects for Fun and Profit

, , , , ,

    Custom objects were one of those things I never really saw the point of when I read about them. It wasn’t until I actually messed around with them that I really understood their uses. They’re especially good for generating reports that pull data from multiple sources.  For example, let’s say your manager has requested a report of all the computers in your Accounting department. He wants to know their hostname, their IP Address, their Make/Model, and their Serial Number.  Now, there’s no built in function or class (that I know of) that will return all of those pieces of information, so we’ll have to pull from multiple data sets.  For this example, we’re going to need Active Directory, the Win32_BIOS class, and the Win32_ComputerSystem class. For the sake of argument, let’s say we’ll also need the Get-CompOU script from the previous post and the hostname of a computer in the Accounting department’s OU to get started.
An example asset report script can be downloaded here:
https://goo.gl/5bB03A

Step 1: Generate a list of all the computers needed for this report
$allComps = Get-ADComputer -SearchBase (Get-CompOU AccountingPC1) -filter * -properties * 

      This gives us an array of all the computer objects hosted in the same OU as the Accounting PC we started with. We can get the hostname and IP information just from these objects, but we need more.

Step 2: Enumerate all the computer objects and start data mining
ForEach ($comp in $allComps)
{
        $SWMI = Get-WmiObject -ComputerName $comp.Name -class Win32_ComputerSystem
        $BWMI = Get-WmiObject -ComputerName $comp.name -class Win32_BIOS

We’re not quite done with the loop yet, but this shows how we’re invoking the WMI classes needed for each computer.  Step 3 takes place within the same loop.

Step 3: Create the custom object to hold our required data
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name “Hostname” -Value $comp.Name
$object | Add-Member -MemberType NoteProperty -Name “DNSName” -Value $comp.DNSHostName
$object | Add-Member -MemberType NoteProperty -Name “Serial” -Value $BWMI.SerialNumber
$object | Add-Member -MemberType NoteProperty -Name “IPAddress” -Value $comp.IPV4Address 
 
$object | Add-Member -MemberType NoteProperty -Name “Make” -Value $SWMI.Manufacturer
$object | Add-Member -MemberType NoteProperty -Name “Model” -Value $SWMI.Model 

$allObj += $object
 }
 This one is pretty straight forward. It’s a lot of text just to say that you’re adding properties to an item you created, assigning those properties values based on multiple classes created in Step 2, and then adding that object to an array of objects.  You can type $allObj = @() at the beginning of your script, but it isn’t required.

Step 4: Profit
    At this point, we can dump our report out to a CSV, or we can just output to the screen.  Typing $allObj will just output to the screen, but if we want to make a report our management will be proud of:
$allObj | export-csv -path C:usersAdminDocumentsAssetReport.csv -NoTypeInformation

And we’re done!  Now, if you’re working in an environment with SCCM or other management software, these reports might be more easily generated by querying that database. However, this will give you up to the minute accuracy as the WMI queries are done live.  If there’s anything you want to see, leave me a comment, and I’ll add that to my next post.  

A quick (and useful) PowerShell script

, ,

    Whether you’ve been in the IT game for years or are just starting out, there are a few simple tools you’ll find yourself relying on almost every day to do tasks so basic, you’ll wonder why Microsoft doesn’t make them just a little easier to do.   The object of this post is to give you a couple small tools that will make your life easier.

Cmdlet 1: Get-CompOU – return the Organizational Unit of any hostname
     If you manage an Active Directory network of any size, you’ll probably wind up troubleshooting Group Policy, and one of the major things that decides what policies your computer receives is the Organizational Unit (OU) where your computer resides.  Personally, I’m a lazy IT guy. I don’t like opening ADUCs, right-clicking the domain, clicking search, typing my computer name (only to realize I forgot to select “Computer” from the drop down), clicking Search, right clicking my computer name, selecting properties, and then finding out the Object tab isn’t there because I forgot to turn on Advanced Features, which means I get to close out of my search and start the whole thing over again.  Even once you’ve found it, now you have to memorize or copy it so you can find your problem child computer and move it to the right OU.
    Alternatively, you can have some simple PowerShell scripts attached to your profile that will do it all in one line.  You can download the script here: https://goo.gl/Bv90kk, but I’ll also go through what makes it tick.

Code:

Function Get-CompOU ($computerName){
    $comp = get-adcomputer $computerName
    if ($comp -eq $null) {write-host -ForegroundColor Red “Computer object $computerName was not found”; return}
    else {
        $compOU = get-adorganizationalunit  ($comp.distinguishedname.substring($comp.distinguishedname.indexof(“,”)+1))
        return $compOU
        }
} 
 

First off, I make all of my scripts that aren’t quick and dirty one-liners as Functions. This means that once I’ve imported my profile, I can just type “get-compou [somehostname],” and my computer will know what to do just the same as if I had typed “get-childitem” or “get-wmiobject.”  The “$computerName” variable is what’s passed as the first argument.   The second line calls get-adcomputer to return the AD computer object of the host name. Then, we check to make sure the computer name was actually found. If it wasn’t, it’ll tell you and return nothing.  If it was found, then we find the OU that matches the one given in the Distinguished Name of the computer and return that object.
      In case you’re wondering why we don’t just return the OU name that we extracted from the Distinguished Name, it’s because I want to return an actual AD OrganizationalUnit object instead of just a string. That gives us a lot more power and freedom with what we can do. It’s also why we use return at the end instead of a write-host.
     For example:  TechPC1 is able to run regedit no problem.  TechPC2 gets an error stating that regedit is disabled by his system administrator, even though 1 and 2 should be getting the same policies.  Our Sys Admin runs Get-CompOU TechPC1 and finds that PC1 is in the correct OU.  Running Get-CompOU TechPC2 reveals that PC2 was accidentally placed in the same OU as the regular production user machines.  From here, our Sys Admin types  get-adcomputer TechPC2 | move-adobject -TargetPath (Get-CompOU TechPC1). 
    Viola! That computer has been moved.  Personally, I find this easier than all the right clicking, searching, copying, and manually moving. The nice thing with PowerShell being so extensible is that the foreachobject cmdlet gives you the ability to run this script against an entire text file of names, or generate a report of every computer in a given OU.
    I hope you’ve found this useful. Stay tuned for the next post.
                                           

Remediate unquoted service path vulnerability via compliance item.

, , , , , ,

Remediate unquoted service path vulnerability

I have now been to three different environments that have this vulnerability. In all three environments this was one of the top offenders per total count of vulnerability. This not something that is necessary a problem that will cause outages if not patched, but potential for malicious programs to run.There are already published posts with great explanation of the vulnerability from CommonExploits, and Tennable. The scope of this blog post will be how to identify the vulnerability, how to manually remediate it, and how to auto-remediate it via the compliance item.

 

To identify unquoted service path vulnerabilities locally on your system: 
Launch Elevated powershell > type the comand below

 

cmd /c  ‘wmic service get name,displayname,pathname,startmode |findstr /i “auto” |findstr /i /v “c:windows\” |findstr /i /v “””‘

Below we see that we do have 1 item that is vulnerable. 

unquoted service path



This is what the ACAS Scans are finding however it does not tell you where to correct this within the registry. To remediate this we will have to locate the object within the registry and add quotes to the path. I  believe there is a way to configure reporting to be more verbose to include the registry location but I am unfamiliar with the ACAS tool. 

ACAS SCAN BEFORE IT IS FIXED

 

unquoted service path
Manual Fix
          Launch Regedit.exe as Admin
          Navigate to HKLMSystemCurrentControlSetServices
          Search by Edit > Find > and enter the vulnerability. For our example we will search “AERTSr64”
          Select the string for our vulnerability
          Add quotes around the value

 

unquoted service path
unquoted service path

ACAS SCAN AFTER IT IS FIXED

NOTE: there is no screenshot for finding this on ACAS scans as it is remediated
 

Automated Fix via Compliance Item

We will create a compliance Item to discovery systems with the unquoted service path vulnerability and then remediate it. 

Discovery Script 

 

$value = cmd /c  ‘wmic service get name,displayname,pathname,startmode |findstr /i “auto” |findstr /i /v “c:windows\” |findstr /i /v “””‘
if ($value -like “* *”)

 

   {

 

   1

 

   }

 

   else

 

   {

 

   0

   }

 

unquoted service path

Remediation Script 

Option Explicit
Const HKLM = &H80000002
Dim objWMIService
Dim colServices, objService
Dim strComputer
dim quote: quote=chr(34)
Dim strPathName
Dim strCommand,strArgs,message
Dim bBatch: bBatch = False
dim wshShell:Set wshShell = WScript.CreateObject(“WScript.Shell”)
Dim i, iCount, objReg
Dim bDebug:bDebug = False
‘If not CScript, re-run with cscript…
If (Not IsCScript()) Then
                For i  = WScript.Arguments.Count -1 to  0 Step -1
                                strArgs = WScript.Arguments(i) & Space(1) &  strArgs
                Next
                WshShell.Run “CScript.exe ” & quote & WScript.ScriptFullName & quote & space(1) & strArgs, 1, true
    WScript.Quit                  ‘…and stop running as WScript
End If
If WScript.Arguments.Count = 1 Then
                bBatch = True
                strComputer = WScript.Arguments(0)
Else
                strComputer = wshShell.ExpandEnvironmentStrings(“%COMPUTERNAME%”)
End If
If strComputer = “” Then WScript.Quit
strComputer = UCase(strComputer)
WMICX()
Main()
If Not bDebug Then
                WScript.Echo iCount & ” Unquoted Service Path(s) were fixed on ” & strComputer
End If
”””””’ Functions and Subs ”””””””””
Sub WMICX()
                ‘WMI connection
                On Error Resume Next
                Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\” & strComputer& “rootcimv2”)
                If Err.Number <> 0 Then
                                message = “Error reaching or connecting to ” & strComputer
                                If not bBatch Then
                                                MsgBox message, vbcritical + vbinformation,”Failure”
                                Else
                                                WScript.Echo message
                                End If
                                WScript.Quit(100)
                End If
                On Error GoTo 0
End Sub
Sub  Main()
                On Error Resume Next
                Set objReg = GetObject(“winmgmts:\” & strComputer & “rootdefault:StdRegProv”)
                Set colServices = objWMIService.ExecQuery (“SELECT pathname,displayname FROM Win32_Service”)
                iCount = 0
                For Each objService in colServices
                                strCommand = “”
                                strArgs = “”
                                strPathName = objService.PathName
                                ‘Parse the Pathname, which is the command
                                ‘This is a little complicated.
                                Dim iLastSlash, iProgEnd
                                ‘find the last character
                                iLastSlash = InStrRev(strPathName,””)
                                ‘look for a space beginning at last , put location in iProgEnd
                                iProgEnd = InStr(iLastSlash,strPathName,Space(1))
                              
                                ‘iProgEnd will be zero if no arguments
                                If iProgEnd > 1 Then
                                                strArgs= trim(Mid(strPathName,iProgEnd))
                                Else
                                                strCommand = strPathName
                                End        If
                              
                                If Left(strPathName,1) <> quote And InStr(strCommand,Space(1)) Then
                                                wscript.echo “Found ” & objService.DisplayName & ” Service with command line:” & _
                                                               VbCrLf & vbtab & objService.PathName
                                                FixService objService.Name, strCommand, strArgs
                                End If
                Next
End Sub
Sub FixService (strSvsName, strCommand,strArgs)
                If bDebug Then Exit Sub
                ‘add a space only if there are arguments
                If Len(strArgs) > 0 Then strArgs = strArgs & Space(1) & strArgs
                Dim strValue
                Dim strRegPath,strImagePath
                strRegPath= “SYSTEMCurrentControlSetServices”& strSvsName
                strImagePath = quote  & strCommand & quote & strArgs
                WScript.Echo “Setting Command line to ” & strImagePath
                objReg.SetExpandedStringValue HKLM,strRegPath,”ImagePath”,strImagePath
                iCount = iCount +1
End Sub
Function IsCScript()
    If (InStr(UCase(WScript.FullName), “CSCRIPT”) <> 0) Then
        IsCScript = True
    Else
        IsCScript = False
    End If

End Function

unquoted service path

 
  
At this point go ahead and deploy the configuration item you have just created. I would recommend only performing this against workstations but only after serious testing. 

Unfortunately I forgot to take a screenshot when I fist deployed this compliance item, but I do have one after half the systems checked for policy.

unquoted service path

 After systems get policy we are now looking at a very high remediation percentage.

unquoted service path
NOTE: this will run on its own automatically but you can individually set this to run. This will be by launching the configmr client > Configurations Tab > Select the compliance item > Evaluate
unquoted service path
This is what a successful report looks like for the configuration item.
unquoted service path

The compliance item can be downloaded on Technet here.

I would like to thank Khalid Al Alul, and Ricky Richard for their time in working on this configuration item.