Click an Ad

If you find this blog helpful, please support me by clicking an ad!

Tuesday, June 30, 2015

So, Which of My Computers is Using Cached Exchange Mode?

I know a lot of scripts that I write about on here can be rendered unnecessary by good use of the technology available to me. Unfortunately, it seems that often there is something in the way (politics, money, manual process, complexity, etc) that makes it a whole lot easier for me to just script out something and send myself a report once in a while.

I don't know about you, but I've had my share of problems with Outlook caused by cached mode being enabled. I know this is controllable by Group Policy, but we have a lot of people that use Outlook Calendars extensively, and they need cached mode on. This is one of those cases where it's easier to run this monthly and keep things tight, than it would be for me to try and scope a group policy to omit people from all over the place, and remember to incorporate new hires that match this profile.

The trick here was to find out how I would know if a client connected and was on cached mode. The best option, it turned out, was to look in the RPC logs of the Exchange server itself.

#########################################################
# BEGIN SCRIPT
#########################################################
# Phase One: Preperation
#########################################################

#Import Active Directory Module
Import-Module activedirectory

#Function to find Hostnames from IP Addresses
Function Get-HostFromIP
{
$IP = $args[0]
$result = $null
$result = [System.Net.Dns]::gethostentry($ip)
If ($Result){
    $DNS = [string]$Result.HostName
}
Else
{
    $DNS = "No HostName Found"
}
$DNS
} #End Function

#Path Variables
$ExchangeLogFolder = "\\mailserver\c$\Program Files\Microsoft\Exchange Server\V14\Logging\RPC Client Access"
$LocalHoldingFolder = "C:\Logs\ExchangeCachedMode"
$OutputFile = "C:\Temp\Cached Mode On - Desktops.csv"

#Email Variables
$SMTPServer = "mail.contoso.com"
$To = "reporting@contoso.com"
$From = "helpdesk@contoso.com"
$Body = "See Attached"

#Remove any output file if it already exists
Remove-item $OutputFile -force -ErrorAction SilentlyContinue

#Delete any pre-existing file in the Local Holding Folder
Get-Childitem -Path $LocalHoldingFolder | remove-item -force -ErrorAction SilentlyContinue

#########################################################
# Phase Two: Copying over the RPC logs from Exchange
#########################################################

#Copy the files
$Files = get-childitem -Path $ExchangeLogFolder | select fullname
Foreach ($File in $Files){
    Copy-Item $File.fullname -Destination $LocalHoldingFolder
}

#Remove the first 5 lines of each LOG file, change the fields row, and output in a consistent CSV format
$Files = get-childitem -Path $LocalHoldingFolder | select fullname
Foreach ($File in $Files){
    $Text = Get-Content $File.Fullname
    $Output = $Text[4..($Text.count)]
    $Output[0] = $Output[0] -replace "`#Fields: ",""
    $Newfile = (($File.Fullname)+"OUT.csv")
    $Newerfile = (($File.Fullname)+"FINAL.csv")
    $Output | %{$_ | Add-content $NewFile}
    $NewFileContent = Import-csv $Newfile
    $NewFileContent | select client-name,client-mode,client-ip | export-csv -NoTypeInformation $Newerfile
} #End Foreach

#Remove the old files that I don't need anymore
Get-childitem $LocalHoldingFolder -Filter "*.LOG" | %{Remove-Item $_.fullname -Force -ErrorAction SilentlyContinue}
Get-childitem $LocalHoldingFolder -Filter "*.LOGOUT.csv" | %{Remove-Item $_.fullname -Force -ErrorAction SilentlyContinue}

#########################################################
# Phase Three; Merging the CSV files
#########################################################

#Get some info
$CSVFilePath = $LocalHoldingFolder

#Get info from the CSV file path
$CSVFiles = get-childitem $CSVFilePath | select fullname, name

#Initialize/Clear the output array
$Output = @()

#Cycle through and add csv content to array
foreach($CSV in $CSVFiles) {          
    if(Test-Path $CSV.fullname) {          
        $FileName = [System.IO.Path]::GetFileName($CSV.FullName)          
        $temp = Import-CSV -Path $CSV.fullname | select *, @{Expression={$FileName};Label="FileName"}          
        $Output += $temp          
    } else {          
        Write-Warning "$CSV.fullname : No such file found"          
    }
} #End Foreach

#Export Array content to specified output file
$Output | Export-Csv -Path ($LocalHoldingFolder + "\temp.csv") -NoTypeInformation

#Remove the old files that I don't need anymore
Get-childitem $LocalHoldingFolder -Filter "*.LOGFINAL.csv" | %{Remove-Item $_.fullname -Force -ErrorAction SilentlyContinue}

#########################################################
# Phase 4: Getting the data together
#########################################################

#Import the data for further refinement
$Content = Import-CSV ($LocalHoldingFolder + "\temp.csv")

#Select only the properties I want
$Refined = $Content | select client-name,client-mode,client-ip

#Run through some filters
$Refined2 = $Refined | where-object {
    #Don't care about entries that list no ip address
$_."client-ip" -ne "" -and
#Here's the interesting bit: Classic means cached exchange mode is NOT on
    $_."client-mode" -ne "Classic" -and
#There are some IP scopes that I can't do anything about
    ($_."client-ip" -like "*192.168.98*" -or
    $_."client-ip" -like "*192.168.99*") -and
#I don't care about Exchange traffic
    $_."client-name" -notlike "*Exchange*"
    }

#Removing duplicate IPs
$Refined2 = $Refined2 | Sort-Object client-ip -Unique

#Initialize a new array
$Refined3 = @()

#Put the same data into the new array, but also include the hostname based on the IP
Foreach ($Item in $Refined2){
    $SubRefined3 = New-Object System.Object
    $ClientHostName = Get-HostFromIP $Item."Client-IP"
    $ClientDescription = (Get-ADComputer ($ClientHostname -replace (".contoso.com","")) -Properties * | select Description).Description
    $SubRefined3 | Add-Member -type NoteProperty -name ClientName -value $Item."Client-Name"
    $SubRefined3 | Add-Member -type NoteProperty -name ClientMode -value $Item."Client-Mode"
    $SubRefined3 | Add-Member -type NoteProperty -name ClientIP -value $Item."Client-IP"
    $SubRefined3 | Add-Member -type NoteProperty -name ClientHostName -value $ClientHostName
    $SubRefined3 | Add-Member -type NoteProperty -name ClientDescription -value $ClientDescription
    $Refined3 += $SubRefined3
} #End Foreach

#Remove any items where a DNS hostname could not be found
$Refined3 = $Refined3 | where-object {$_.ClientHostname -notlike "No Hostname Found"}

#Do AD Lookups to remove any computers that are laptops, based on AD OU. I have no issue with laptops being on cached exchange mode.
$Refined3 = $Refined3 | where-object {((get-adcomputer ($_.ClientHostName -replace (".contoso.com","")) | select DistinguishedName).DistinguishedName) -notlike "*OU=Laptop*"} | sort-object ClientHostName

#Filter out any hostnames that I don't want in the report
$Refined4 = $Refined3 | Where-Object {
    $_.ClientHostName -notlike "def*" -and
    $_.ClientHostName -notlike "ghi*" -and
    $_.ClientHostName -notlike "No Hostname Found"}

#Final Export, excluding computers where Cached Exchange Mode is needed
$Refined4 | Where-Object {$_.ClientHostName -notlike "A123456*" -and
$_.ClientHostName -notlike "B4545875*"} | export-csv $OutputFile -NoTypeInformation

#Get some counts
$Count = (($Refined4 | Measure-Object).count)
$CountString = (($Refined4 | Measure-Object).count).ToString()
$Subject = "PS Report - Clients Using Cached Exchange Mode - $CountString"

#Only send an email if there are more than zero results
If ($Count -gt 0){
    #Send Email
    Send-Mailmessage -To $To -From $From -SMTPServer $SMTPServer -Subject $Subject -Body $Body -Attachments $OutputFile
} #End If

#Remove Temp Files
Remove-Item $OutputFile -Force -Erroraction SilentlyContinue
Remove-Item ($LocalHoldingFolder + "\temp.csv") -Force -Erroraction SilentlyContinue

#########################################################
# END SCRIPT
#########################################################

My Daily Certificate Authority Check

Earlier this year I rolled out my organizations own Public Key Infrastructure. Certificates.

I use the script below to send me an email that includes the following in the subject:
How many days until the next certificate will expire
A list of all issued certificates
How many requests are pending.

Like this subject, for example: PS Report - Issuing CA Info (Next Expiration is 296 days from now, 0 Requests Pending). A list of all issued certificates, with common name, issue date, and the template they are based on is attached as an HTML file.

A prerequisite for this script is the PS PKI Module, which can be found here on Codeplex.

This script runs from my issuing certificate authority server.

######################################################################
# BEGIN SCRIPT
######################################################################

#Import the PS PKI Module
Import-Module PSPKI

#Variables
$TempFile = "C:\Temp\CA_Report.html"
$Today = get-date
$To = "reportingaddress@contoso.com"
$From = "me@contoso.com"
$SMTPServer = "mailserver.contoso.com"

#Get the CA Name
$CAName = (Get-CA | select Computername).Computername

#Get Details on Issued Certs
$Output = Get-CA | Get-IssuedRequest | select RequestID, CommonName, NotAfter, CertificateTemplate | sort Notafter

#Take the above, and exclude CAExchange Certs, Select the first one, and get an integer value on how many days until the earliest renewal is necessary
$RelevantInfo = ($Output | where-Object {$_.CertificateTemplate -notlike "CAExchange"})
$EarliestExpiryInteger = ([math]::abs(($Today - ($RelevantInfo[0].Notafter)).Days)).ToString()

#Write the Relevant Info to a temp file
$RelevantInfo | ConvertTo-HTML | out-file $TempFile

#Get Details on Pending Requests
$Pending = Get-CA | Get-PendingRequest

#Get number of pending requests - If pending requests is null, then PendingCount is left at zero
If ($Pending){$PendingCount = ($Pending | Measure-Object).count}
Else {
$PendingCount = 0
$Pending = "`r`nNone"
} #End Else
$PendingCountStr = $PendingCount.ToString()

#Make the mail body
$Body = "See Attached"

$Subject = "PS Report - Issuing CA Info (Next Expiration is $EarliestExpiryInteger from now, $PendingCountStr Requests Pending)"

Send-mailmessage -To $To -From $From -SmtpServer $SMTPServer -Subject $Subject -Body $Body -Attachments $TempFile

Remove-Item $TempFile -force

######################################################################
# END SCRIPT
######################################################################

Get a List of All Files with a Certain Extension

It happens sometimes that I need to find all files with a certain extension on a given drive. I accomplish this in a more automated way through Microsoft's File Server Resource Manager (FSRM), These cases include reporting on ISO, MP3, and video files that I don't really want cluttering up my file servers. Occasionally however, I just want a list of Access database files, or something. The outlier cases. For that, I have this little script:

#####################################################################
# BEGIN SCRIPT
#####################################################################

#This script prompts for a file extension and a root path, then searches recursively within that path for that extension and sends you a report of all the files.

#Get Hostname
$Hostname = ($env:computername)

#Prompt for file extension to search for
$ext = Read-host "File Extension (do not enter a period)"
$ext = $ext.ToUpper()

#Create Temp File Location
$TempFile = "C:\Temp\FileQuery $ext.csv"

#Get Root Path to search in
$PathToSearch = Read-Host "Path to search (i.e. P:\)"

#Get your email address
$EmailAddress = Read-Host "Your email address (to send the report to)"

#Conduct search, export to CSV (Temp File)
get-childitem -Path $PathToSearch -Filter *.$ext -recurse | select name, Length, DirectoryName | export-csv -NoTypeInformation $TempFile

#Send CSV via email
Send-Mailmessage -to $EmailAddress -from me@contoso.com -smtpserver mailserver.contoso.com -Subject "$Ext Files on $Hostname in $PathToSearch" -Body "See Attached" -attachments $TempFile

#Delete the Temp File
remove-item $Tempfile

#####################################################################
# END SCRIPT
#####################################################################

Getting my Windows Clients Patched

Most of the time my WSUS group policies do the job of making my client systems update when they're supposed to, but other times it just doesn't work out. On top of that, I don't have good reporting on the patch levels of my systems. Yeah I know. It's on my list.

In the meantime, I use the script below to force computers to check for updates, and then open up a separate (awesome, not mine!) Powershell GUI app that I patch them with.

I also use this process to reboot all of my "client" machines if needed. For example, when a version of Flash comes out that fixes a vulnerability being actively exploited in the wild. Ahem. I patch Flash via Group Policy on system startup. For this, I just skip the patching functionality and use PosPAIG to reboot computers.

You can find PoshPAIG at codeplex here. Seriously, if you patch systems, even if you aren't going to use this script, check it out. I use it for my servers too. Saves me SO much time.

The process is as follows:
1. Get all the client computers from AD (with some exceptions).
2. Force them to check for updates.
3. Wait.
4. Feed the list into PoshPAIG.
5. Patch and reboot (or just reboot).

Heeeere we go - I'll talk through the scripts via the built-in documentation.

################################################################################
BEGIN SCRIPT
################################################################################
# Phase One: Getting our Data Together
################################################################################

#The path to PoshPAIG (it's a Powershell GUI that allows you to scan/patch multiple systems)
$PoshPAIGFolder = "C:\PS\PoshPAIG\Start-PoshPAIG.ps1"

#File for storing a list of which systems are initially powered on
$OutputFilePoweredOn = "c:\temp\WsusMegaCheckIn_PoweredOn.txt"

#File for storing a list of which systems weren't powered on, so I can deal with them later.
$OutputFilePoweredOff = "C:\Temp\WSUSMegaCheckIn_PoweredOff.txt"

#Remove any existing outfiles, ignoring errors
Remove-Item $OutputFilePoweredOn -force -erroraction silentlycontinue
Remove-Item $OutputFilePoweredOff -force -erroraction silentlycontinue

#Import the AD Module
import-module activedirectory

#Get the computers from AD, with some exceptions: I don't want anything from the computers container or Servers OU, nor Domain Controllers, NAS devices, or WAPs. 
$PreLimComputers = get-adcomputer -filter * -properties * | where {
$_.distinguishedname -notlike "*CN=Computers*" `
-and $_.distinguishedname -notlike "*OU=Servers*" `
-and $_.distinguishedname -notlike "*OU=Domain Controllers*" `
-and $_.Name -notlike "NAS*" `
-and $_.Name -notlike "WAP*"
}

################################################################################
# Phase Two: Refining our data a bit
################################################################################

#Now, there are a few people who get all bent out of shape if I reboot their computers (like me, and the computer I'm running this script from). Instead of adding them statically to the list above, I've created an AD group that contains their computer accounts. This allows me to use this group as an exception to any rebooting-type script that I write. So, here I will  based on NoPatchReboot Group Membership

#Create a blank array
$Groups = @("NoPatchReboot")

#Create a Regex - I'll be honest that I know what this does, but not how it does it. I just can't wrap my head around regex. BUT.....
$RegEx = '^({0})' -f ($groups -join '|')

#Now I'm going to create a new list of computers, where none of the computers are members of my NoPatchReboot AD Group, helped along by the mysterious, enigmatic regex
$Computers = $PreLimComputers | where-object {($_.MemberOf | Get-AdGroup).Name -notmatch $Regex} | select name | sort name


################################################################################
# Phase 3: Let's do some Work
################################################################################

#Foreach computer, if it is pinged successfully, send it a remote command to check for updates
#Also add it to the "Powered On" text file that you can use in PoshPAIG

Foreach ($computer in $Computers){
    $System = $Computer.Name
    If ((Test-Connection -ComputerName $System -Count 1 -Quiet) -eq $true){
        Invoke-Command -ComputerName $System -ScriptBlock {c:\windows\system32\wuauclt.exe /detectnow}
        Write-host -Foregroundcolor GREEN "$System forced to check for updates"
        $system | add-content $OutputFilePoweredOn
    } #End If
} #End Foreach

#Start PoshPAIG for me
start-process powershell -ArgumentList "-File $PoshPAIGFolder"

Write-Host "Import $OutputFilePoweredOn to patch live systems"

################################################################################
END SCRIPT
################################################################################

At this point, PoshPAIG is open, and I would import the PoweredOn text file, then scan and patch those systems.

I was going to get into my WHOLE script, but I decided that it was a little too specific in a not-applicable-to-most kind of way.

The gist is, though, that while PoshPAIG was doing it's thing, I would initialize the next phase of my script, which uses Wake-On-LAN (WOL) packets to turn machines on. Is the machine in another subnet? But, WOL packets are broadcast and can't be routed! Oh noes! Pffft. Got it covered. I know a server on site that's all to happy to send out WOL packets to machines I target.

I'll put in some code here, because it's generally useful, but this is just code snippets now.....

The first thing we need are MAC addresses for the WOL packets. I have spiceworks pull a report of IP Addresses and MAC Addresses every day and dump it out to a CSV just for this purpose. Once my Domain Controllers get onto Server 2012 I plan (hope) to replace this kludge with the new Powershell DHCP commandlets.
$MACAddressTableFromSpiceworks = Import-CSV "\\spiceworks\rpt\report-174.csv"

Once I get a MAC address, I use this to send a WOL packet (to computers on-site):
$MacByteArray = $MacAddress -split "[:-]" | ForEach-Object { [Byte] "0x$_"} 
[Byte[]] $MagicPacket = (,0xFF * 6) + ($MacByteArray  * 16)
$UdpClient = New-Object System.Net.Sockets.UdpClient
$UdpClient.Connect(([System.Net.IPAddress]::Broadcast),7)
$UdpClient.Send($MagicPacket,$MagicPacket.Length) | out-null
$UdpClient.Close() | Out-Null

What's neat about the above (which I didn't make, btw) is that it doesn't care if you use hyphens or colons as the delimiter, by using -split.

Now, if the system is at another site, based on IP address, I would send a list of systems at that site into a text file there, and then call a WOLScript to send the WOL packets to those computers, like so:

$process=[WMICLASS]"\\$SisterSiteServer\ROOT\CIMV2:win32_process"
$result=$process.Create("powershell.exe -File C:\ps\WOLScript.ps1")
$ProcessID = $result.processID

I want this script to just wait until the script at the sister site is finished running
Only when the process initiated above completes will $Running be $null, and then the while loop will end

$Running = 1
While ($Running -ne $null){
Start-Sleep -seconds 30 #wait 30 seconds
$Running = (get-process -id $ProcessID -ComputerName $SisterSiteServer -ea 0) #Check the process again
} #End While

Aaaaand now I take a break while those computers boot up ad settle down. Stop. Coffeetime.
Write-Host "Sleeping for 5 minutes to allow powered on computers to settle"
Start-Sleep -Seconds 300

And at this point I pretty much start all over, forcing those systems to check for updates
Foreach ($computer in $PoweredOffSystems){
    $System = $Computer
    If ((Test-Connection -ComputerName $System -Count 1 -Quiet) -eq $true){
        Invoke-Command -ComputerName $System -ScriptBlock {c:\windows\system32\wuauclt.exe /detectnow}
        Write-host -Foregroundcolor GREEN "$System forced to check for updates"
    } #End If
    Else {
        Write-host -Foregroundcolor RED "$System is Offline"
    } #End Else
} #End Foreach

Now I take note of any non-responsive systems so that I can investigate them later to find out why WOL didn't work.

Nowadays, that's usually because the system is on the wireless. I briefly considered removing laptops from the initial list from AD, but hey, some of them might be docked, so I'll get them while I can.