Jun 022016
 

So I have not posted anything to my blog for quite some time. I’ve decided to fix that, and I’m also changing the focus of my blog slightly from a broad catch-all for my technology interests to a more focused blog on what I’m learning with PowerShell, operating systems, and hardware.

First up is a new script I created to generate a server uptime report. Is also uses Microsoft SCCM WMI classes to determine if a reboot is pending (useful if updates delivered via SCCM didn’t trigger a reboot).

I also decided that I didn’t want to maintain a list of servers to query, so I have my script querying Active Directory OUs to compile the server list.

There are some things that you need to set in order to get this operational. The first is the list of OUs you wish to search. The other is information for the email report. The email stuff can be skipped if you wish, but I’ve found it quite useful, as long as you have an open SMTP relay.

Some things I need to do:

  • Allow toggle of CCM query. Not everyone has SCCM running.
  • Add optional authentication to email for relays that are not unauthenticated.
  • Additional error handling.

So here you go, and as always, let me know if you see something I can improve upon. I am learning this stuff after all.

#requires -Version 2 -Modules ActiveDirectory

# Author: Robert Hollingshead 
# Version: 1 
# Version History: First version. 
# Purpose: Get a list of servers based on arbitrary OU's, and then check each server for uptime. 
# Also grab pending reboot flag from CCM. Send as a report to an email address. 
# To-Do List: Allow toggle of CCM reboot-pending. Add optional authentication to mail. Additional error handling.

# Uncomment this to disable progress bar.
# $ProgressPreference = 'SilentlyContinue'

# Enter OUs to search here. One per line, follow the format below, omit comma on last line.
# You definately want to edit this stuff.
$OUsToSearch = @(
  'OU=OU=Servers,DC=Contoso,DC=net', 
  'OU=Domain Controllers,DC=Contoso,DC=net'
)

# Enter your mail information here. 
$MailSender = '{sender email address here}'
$MailRecipients = '{recipient list here}'
$MailSubject = "Server UpTime Report for $(Get-Date -Format d)"
$MailBody = $MailSubject + ' is attached'
$MailServer = '{smtp server here}'

# Change nothing below this line. 
# ===============================

# Grab the class so that we can use convert to date fu nction. 
$WMI = [wmiclass]'\\.\root\cimv2:win32_operatingsystem'

# Set up some arrays. 
[system.array]$UpTimeReport = $null
[system.array]$Servers = $null

# Query Active Directory
ForEach ($OU in $OUsToSearch) 
{ 
  $OUResult = $null
  Try 
  { 
    $OUResult = Get-ADComputer -SearchBase $OU -Filter * -ErrorAction Stop
    $Servers = $Servers + $OUResult
  }
  catch 
  {
    Write-Warning -Message "Failed to get servers in $OU"
  }
}

# Set up for progress bar.
[long]$totalitems = $Servers.count
[long]$progress = 1
[long]$percentage = 0

# Query each server for uptime. 
foreach ($Server in $Servers) 
{
  # Calculate percent complete and write-progress (if enabled)
  $percentage = ($progress * 100) / $totalitems
  Write-Progress -Activity 'Querying servers.' -PercentComplete $percentage `
  -Status "$percentage% complete..." -CurrentOperation "Checking $($Server.name)."

  # Attempt to get last bootup time and local date/time, then calculate. If fails, handle error. 
  try 
  { 
    $Stopwatch = Measure-Command -Expression {
      $OSProps = Get-WmiObject -ComputerName $Server.name -Class win32_operatingsystem -Property LocalDateTime, LastBootupTime
      $UpTime = $WMI.ConvertToDateTime($OSProps.LocalDateTime) - $WMI.ConvertToDateTime($OSProps.LastBootUpTime)
      $LastBootTime = $WMI.ConvertToDateTime($OSProps.LastBootUpTime)

      # Use CCM to determine if a reboot is pending.
      $CCMWmi = [wmiclass]"\\$($Server.name)\ROOT\ccm\ClientSDK:CCM_ClientUtilities"
      $Reboot = ($CCMWmi.DetermineIfRebootPending()).RebootPending
    }
    $ServerUptime = "$($UpTime.days)+$($UpTime.Hours):$($UpTime.Minutes):$($UpTime.Seconds).$($UpTime.Milliseconds)"
    $TimeToRetrieve = "$($Stopwatch.Minutes):$($Stopwatch.Seconds).$($Stopwatch.Milliseconds)"
  }
  catch 
  {
    $ServerUptime = 'Not Available'
    $LastBootTime = 'Not Available'
    $TimeToRetrieve = 'Failed To Retrieve'
    $Reboot = $false
  }

  # Create object to be added to report. 
  $CurrentServer = New-Object -TypeName PSObject
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'ServerName' -Value $($Server.name) 
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'UpTime' -Value $($ServerUptime)
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'LastBootTime' -Value $($LastBootTime)
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'TimeToRetrieve' -Value $($TimeToRetrieve)
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'TimeRetrieveFinished' -Value $(Get-Date -Format T)
  $CurrentServer | Add-Member -MemberType NoteProperty -Name 'RebootPending' -Value $($Reboot)
  
  # Add to report object.
  $UpTimeReport = $UpTimeReport + $CurrentServer
  
  # Increment progress counter for progress bar. 
  $progress++
}

$ReportPath = $env:Temp + '\ServerUpTime.csv'
$UpTimeReport | Export-Csv $ReportPath
Send-MailMessage -From $MailSender -To $MailRecipients -Subject $MailSubject -Body $MailBody -Attachments $ReportPath -SmtpServer $MailServer
Mar 102015
 

One of my latest projects at work was to write a powershell script to remove arbitrarily selected registry entries. Here are some tips for getting along with remove-itemproperty and the registry so that it works the first time. As always, if you have any corrections or something else I could try, let me know. I could have used the reg command but that’d be cheating. ūüėČ

So let’s define a registry path. This should be enough to work with Remove-Itemproperty becuase it does accept paths.

$RegPath1 = 'HKLM:\SOFTWARE\Wow6432Node\Cisco Systems, Inc.\Communicator'

Unfortunately it isn’t. If we run the following:

Remove-ItemProperty -Path $RegPath1 -Name TftpServer1 -Force

Powershell gives us this nice error. Which is fun because in CM12 we are running as “nt authority\system,” the grand kahuna of all accounts. More godly than administrator.

ERROR: Remove-ItemProperty : Requested registry access is not allowed.
CiscoIPC_Regkey_Removal.ps1 (24): ERROR: At Line: 24 char: 12
ERROR: + $RegKey1 | Remove-ItemProperty -Name TftpServer1 -Force
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR: + CategoryInfo : PermissionDenied: (HKEY_LOCAL_MACH...c.\Communicator:String) [Remove-ItemProperty], SecurityException
ERROR: + FullyQualifiedErrorId : System.Security.SecurityException,Microsoft.PowerShell.Commands.RemoveItemPropertyCommand

There is another way to do this. Instead of giving Remove-ItemProperty the path, use it in conjunction with Get-ItemProperty. The following works.

$RegPath1 = 'HKLM:\SOFTWARE\Wow6432Node\Cisco Systems, Inc.\Communicator'

$RegKey1 = Get-ItemProperty $RegPath1

$RegKey1 | Remove-ItemProperty -Name TftpServer1 -Force

In this case were getting an the registry key as an object and passing it to Remove-ItemProperty, which works.

Now for detecting that the key is gone. One of the other issues I encountered is that if you’re going to use if() to see if the value still exists, you need two conditions.

  • The first condition is that value¬†has a value or …
  • The second condition is that the value¬†is equal to $null.

I ran into this existential dilemma whenever I used the first condition only is that the if() would fail if the key value was there but didn’t have any actual value to it, which happens. So we add an -or and check to see if it -eq $null. Like so:

if (($RegKey1.TftpServer1) -or ($RegKey1.TftpServer1 -eq $null)) {
$Checksum++
}

And the $Checksum will be incremented if the value exists as either null or has a value. I’ll leave the significance of the checksum up to you.

As a side note: I really wish compliance settings in SCCM 2012 could allow for direct remediation (removal) of registry keys if they shouldn’t exist, but unfortunately that isn’t the case.

Nov 232014
 

The following detection script accomplishes the following.

  • Determines if virtual memory is automatically managed. The desired configuration according to the script is that the pagefile should be managed manually (true can be changed to false if you want to go the automagic route).
  • If the pagefile is not automatically managed, the script determines if the size of the page file is at least double the amount of visible physical memory.

I’m working on a remediation script, but for now I figure’d I would share the love.

# This script simply checks to see if Windows is handling the page file
# automagically. Then if no, it verifies to make sure that the swap file
# is set at or over twice the available memory. 

$system = get-wmiobject -Class win32_ComputerSystem

if ($system.AutomaticManagedPagefile -eq $true) {
    write-host FALSE
    } else {  

    $mem = get-wmiobject -Class win32_OperatingSystem | select-object TotalVisibleMemorySize,TotalVirtualMemorySize

    [int64]$vismem = $mem.TotalVisibleMemorySize
    [int64]$vrtmem = $mem.TotalVirtualMemorySize

    if ($vrtmem -ge ($vismem * 2)) {
        write-host TRUE
        } else {
        write-host FALSE
    }
}

 

Nov 052014
 

EDIT 11/10/14: I found and corrected a bug in the detection script. There is a chance in certain configurations that the detection script might miss cards. I modified it so that wouldn’t happen.

The following detection and remediation scripts are designed to be placed into as compliance settings in a configuration item in CM12. They are heavily modified from an original powershell function published on the TechNet gallery. Where the original powershell script is a run-once affair, these two scripts will enable you to establish compliance on all desktops, laptops, and especially servers where it is generally not a good idea to power manage your NICs.

I recommend testing before deployment. Note that the remediation script will run and the detection script will show compliance, but where the original script could force a reboot, the remediation script here does no such thing. This is intentional as I believe it would be better in practice to let the compliant machines reboot through other means, such as during a patch cycle or when the end user shuts down for the evening.

The detection script (copy and paste, the lines will remain intact):

# NIC Power Management Detection Script for CM12 Compliance Settings

# Based off of the script found at https://gallery.technet.microsoft.com/scriptcenter/Disable-turn-off-this-f74e9e4a
# Modified by Robert Hollingshead
# November 10th, 2014
# Find more CM12 Compliance Setting scripts at occurative.com!

# This script detects power management status for all physical NICs.

#Original scripts comments:
#find only physical network,if value of properties of adaptersConfigManagerErrorCode is 0,  it means device is working properly. 
#even covers enabled or disconnected devices.
#if the value of properties of configManagerErrorCode is 22, it means the adapter was disabled. 

# This is to calculate compliance. If both of these are equal at the end then all NICs are compliant.
$SettingChecksum = 0
$NICCount = 0

$PhysicalAdapters = Get-WmiObject -Class Win32_NetworkAdapter|Where-Object{$_.PNPDeviceID -notlike "ROOT\*" `
	-and $_.Manufacturer -ne "Microsoft" -and $_.ConfigManagerErrorCode -eq 0 -and $_.ConfigManagerErrorCode -ne 22} 
	
Foreach($PhysicalAdapter in $PhysicalAdapters) {
	$PhysicalAdapterName = $PhysicalAdapter.Name
	
    #check the unique device id number of network adapter in the currently environment.
	$DeviceID = $PhysicalAdapter.DeviceID
	If([Int32]$DeviceID -lt 10) {
		$AdapterDeviceNumber = "000"+$DeviceID
		} Else {
		$AdapterDeviceNumber = "00"+$DeviceID
	}

	#check whether the registry path exists.
	$KeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\$AdapterDeviceNumber"
	
    If(Test-Path -Path $KeyPath) {
		$PnPCapabilitiesValue = (Get-ItemProperty -Path $KeyPath).PnPCapabilities
		If($PnPCapabilitiesValue -eq 0){
			#This adapter isn't compliant!
            $SettingChecksum++		
		}
		If($PnPCapabilitiesValue -eq $null) {
            #This adapter isn't compliant!
		    $SettingChecksum++				
		}
    }			
}


# Are we compliant?
If ($SettingChecksum -eq 0) {
    write-host TRUE
    } else {
    write-host FALSE
}

The remediation script (copy and paste, the lines will remain intact):

# NIC Power Management Remediation Script for CM12 Compliance Settings

# Based off of the script found at https://gallery.technet.microsoft.com/scriptcenter/Disable-turn-off-this-f74e9e4a
# Modified by Robert Hollingshead
# November 5h, 2014
# Find more CM12 Compliance Setting scripts at occurative.com!

# This script turns off power management for all physical NICs.

#Original scripts comments:
#find only physical network,if value of properties of adaptersConfigManagerErrorCode is 0,  it means device is working properly. 
#even covers enabled or disconnected devices.
#if the value of properties of configManagerErrorCode is 22, it means the adapter was disabled. 

$PhysicalAdapters = Get-WmiObject -Class Win32_NetworkAdapter|Where-Object{$_.PNPDeviceID -notlike "ROOT\*" `
-and $_.Manufacturer -ne "Microsoft" -and $_.ConfigManagerErrorCode -eq 0 -and $_.ConfigManagerErrorCode -ne 22} 
	
Foreach($PhysicalAdapter in $PhysicalAdapters) {
    
    $InterfaceChecksum++
	$PhysicalAdapterName = $PhysicalAdapter.Name
		
    # Check the NIC ID Number.
	
    $DeviceID = $PhysicalAdapter.DeviceID
	
	If([Int32]$DeviceID -lt 10) {
		$AdapterDeviceNumber = "000"+$DeviceID
	} Else {
		$AdapterDeviceNumber = "00"+$DeviceID
	}
		
	# See if the registry path exists.
	$KeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\$AdapterDeviceNumber"
		
    If(Test-Path -Path $KeyPath) {
		$PnPCapabilitiesValue = (Get-ItemProperty -Path $KeyPath).PnPCapabilities
		If($PnPCapabilitiesValue -eq 0){
			#setting the value of properties of PnPCapabilites to 24, it will disable save power option.
			Set-ItemProperty -Path $KeyPath -Name "PnPCapabilities" -Value 24 | Out-Null			
		}
		If($PnPCapabilitiesValue -eq $null) {
                #setting the value of properties of PnPCapabilites to 24, it will disable save power option.
				New-ItemProperty -Path $KeyPath -Name "PnPCapabilities" -Value 24 -PropertyType DWord | Out-Null				
		}
	}		
}

write-host TRUE

 

I am always open to improvements to these scripts. If you find something that could use improvement just let me know via the comments below!

Jan 152014
 

If you’re working with SCCM 2012 Desired Configuration Management and want to create a CI for local machine certificates, here’s part 1 of a 2 part equation that is evolving. The remediation script is still in the works, but for right now, here’s a script that does the following.

  1. Query the Local Machine for a list of certs assigned to it.
  2. Check the list of certs for a valid machine certificate.
  3. Verify that the certificate has not expired.
  4. Verify that the certificate has the proper template (that you specify).
  5. And finally, verify that the certificate has the proper subject (again, that you specify).
  6. If items 3, 4, and 5 are all in agreement, echo “True” back to SCCM, or “False” if no machine certs are valid.

Note that there are a few strings you need to specify. $template and $subject need to be set or this isn’t going to work. You can also set $hostname to your hearts content. There’s obviously a lot of different ways you can modify this script, but for creating a CI in SCCM 2012 DCM this is a good start. Have fun!

 
########################################################
### Check for existence of valid machine certificate ###
########################################################
#
# By Robert Hollingshead
# Contributions by Steven Buck
#
# Transform function for Template property from
# http://social.technet.microsoft.com/Forums/windowsserver/en-US/187698d0-5602-4301-9d0c-85e89d948ea2/user-powershell-to-get-the-template-used-to-create-a-certificate?forum=winserversecurity
#
#
# Checks the certificate store for a valid machine
# certificate then outputs True.
#
########################################################

# Function to get Template.
function Transform-Certificate {
[CmdletBinding()]
    param(
 [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
 [Security.Cryptography.X509Certificates.X509Certificate2]$cert
 )
    process {
        $temp = $cert.Extensions | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.20.2"}
 if (!$temp) {
            $temp = $cert.Extensions | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.21.7"}
 }
        $cert | Add-Member -Name Template -MemberType NoteProperty -Value $temp.Format(1) -PassThru
 }
}

# Assume the store doesn't contain a valid cert.
[bool]$ValidCert = $false # Assume false.

# Get hostname+fqdn
$hostname = [system.net.dns]::gethostbyname(($env:computername)).Hostname

# Put the template string to match here. * is wildcard
$template = "*{a chunk of your template name to verify}*"

# Put the subject string to match here.
$subject = "CN=$hostname"

# Analyze each certificate in Local Machine.
foreach ($Certificate in (get-childitem -Path Cert:\LocalMachine\My | Transform-Certificate))
{
    If ($Certificate.Subject -eq $subject)  # If certificate has a proper subject.
    {
        If ($Certificate.NotBefore -le (Get-Date)) # If certificate has not expired.
        {
            If ($Certificate.Template -like $template) # If cert has proper template.
            {
                $ValidCert = $true # It's true!
            }
        }
    }
}

If ($ValidCert -eq $true)
    {
        write-host "True"
    }
else
    {
        write-host "False"
    }
Dec 122013
 

Here’s a helpful tip, see here for more details.

I was working on a script to remove specific Modern packages from Windows 8.1 and I needed¬†a quick way to see what was installed. “get-appxprovisionedpackage -online” yielded the following useless results.

PS C:\windows\system32> get-appxprovisionedpackage -online

DisplayName  : Microsoft.BingFinance 
Version      : 2013.809.632.3676 
Architecture : neutral 
ResourceId   : ~ 
PackageName  : Microsoft.BingFinance_2013.809.632.3676_neutral_~_8wekyb3d8bbwe

DisplayName  : Microsoft.BingFoodAndDrink 
Version      : 2013.820.258.2561 
Architecture : neutral 
ResourceId   : ~ 
PackageName  : Microsoft.BingFoodAndDrink_2013.820.258.2561_neutral_~_8wekyb3d8bbwe

The list is longer, but you get the idea. Anyway it’s completely useless to me. The solution is to use select-object to pick out the PackageName value from the collection of objects. Simple really, just use a pipe along with the “select-object” cmdlet then specify the name of the value you want.:

PS C:\windows\system32> get-appxprovisionedpackage -online | select-object PackageName

PackageName
-----------
Microsoft.BingFinance_2013.809.632.3676_neutral_~_8wekyb3d8bbwe
Microsoft.BingFoodAndDrink_2013.820.258.2561_neutral_~_8wekyb3d8bbwe
Microsoft.BingHealthAndFitness_2013.813.243.3760_neutral_~_8wekyb3d8bbwe
Microsoft.BingMaps_2013.809.2206.5385_neutral_~_8wekyb3d8bbwe
Microsoft.BingNews_2013.809.636.2800_neutral_~_8wekyb3d8bbwe
Microsoft.BingSports_2013.809.637.2803_neutral_~_8wekyb3d8bbwe
Microsoft.BingTravel_2013.809.639.25_neutral_~_8wekyb3d8bbwe

Ahh. Much better. Now I could take that output and figure out which Modern apps to delete. I will probably start with Bing.

May 072013
 

This PowerShell script uses Dism to find all OEM entries and then parses them into a text file. Run as administrator.

###########################################################################
#
# Get-Drivers.ps1
#
# Use Dism to create a list of OEM driver entries. This can also be 
# changed to output other entries as well. Run as administrator.
#
# by Robert Hollingshead
# Contributed to the public domain. Do with it what you will. 
#
###########################################################################

$fileStream = [System.IO.StreamWriter] "C:\Drivers.txt"

[array]$arrayDismParsed = @()

$arrayDismParsed = "Parsed output from Get-Drivers.ps1"

# Populate the array. 
$arrayDismOutput = & dism /online /get-drivers /all /format:table

$arrayDismParsed = $arrayDismParsed + $arrayDismOutput[11]
$arrayDismParsed = $arrayDismParsed + $arrayDismOutput[12]
$arrayDismParsed = $arrayDismParsed + $arrayDismOutput[13]

for ($arrayPos=0;$arrayPos -le $arrayDismOutput.length;$arrayPos++)
{
    if ($arrayDismOutput[$arrayPos] -like "*OEM*") # Find the OEM entries. ;
    {
        $arrayDismParsed = $arrayDismParsed + $arrayDismOutput[$arrayPos]
    }
}

for ($arrayPos=0;$arrayPos -le $arrayDismParsed.length;$arrayPos++)
{
    $fileStream.WriteLine($arrayDismParsed[$arrayPos])
}

$fileStream.Close()
May 072013
 

This script will take a list of print servers and then query each individual server on the list for the installed print drivers. It will then output a report in csv format of all drivers encountered.

###########################################################################
#
# Query listed print servers and report on all drivers discovered.  
#
# by Robert Hollingshead
# Contributed to the public domain. Do with it what you will. 
#
###########################################################################

# Place your server list CSV file here, including path. 
$ServerCSV = "{output csv and path}"

# Place the output CSV file here, including path. 
$OutputCSV = "{output csv and path}"

#Change nothing below this line. 

$credential = Get-Credential
$driver = ""
[system.array]$report = $null

$servers = Import-Csv $ServerCSV

foreach ($server in $servers) 
{

    Write-Host ' ' 
    Write-Host $server.Servername -NoNewLine

    $drivers = Get-WMIObject -class Win32_PrinterDriver -computer $server.Servername -credential $credential

    foreach($driver in $drivers) 
    {

        Write-Host "." -NoNewLine

        $path = $driver.DriverPath.Substring(0,1)

        $output = New-Object PSObject
        $output | Add-Member -membertype noteproperty -Name Servername -Value $server.Servername
        $output | Add-Member -membertype noteproperty -Name Drivername -Value $driver.Name

        $servername = $server.Servername

        $output | Add-Member -membertype noteproperty -Name Driverversion -Value (Get-ItemProperty ($driver.DriverPath.Replace("$path`:","\\$servername\$path`$"))).VersionInfo.ProductVersion

        $report = $report + $output

    }

}

$report | export-csv $OutputCSV -notype
May 072013
 

The following PowerShell script will delete drivers from the driver store that match a keyword. It will also ignore a specific driver if need be. Use to prune older sets of drivers that are no longer needed. Use at your own risk, of course.

###########################################################################
#
# Use pnputil to delete drivers (in this case Xerox) from the driver store. 
#
# by Robert Hollingshead
# Contributed to the public domain. Do with it what you will. 
#
###########################################################################

# Populate the array. 
$arrayPnpOutput = & pnputil -e

for ($arrayPos=0;$arrayPos -le $arrayPnpOutput.length;$arrayPos++)
{
    if ($arrayPnpOutput[$arrayPos] -like "*Xerox") # Find matches for xerox as the publisher. ;
    {
        if ($arrayPnpOutput[$arrayPos+2] -notlike "*11/23/2011 5246.700*") # Do not kill this version..
        {
            $arrayPnpOutput[$arrayPos-1] = $arrayPnpOutput[$arrayPos-1] -replace "Published name :            ",""
            Write-Host "Erasing: " $arrayPnpOutput[$arrayPos-1]
            pnputil -d $arrayPnpOutput[$arrayPos-1]
        }
    }
}