Monday, February 24, 2014

Finding large time changes (windows)

When you are looking at time sync problems on newer Microsoft OS's (2008+), there are several places that may show useful information. Looking in the system log, you can find various events from the source: Time-Service, which tell you what server you are syncing with, if the servers are not available, if you domain controller is advertising time, and other various issues. In addition to that, another source: Kernel-General, may have some useful information. In Event ID #1 of this source, you will see occasional clock changes on the system. It gives both the old DateTime and the new one. This helps show you when large changes to the clock happen, so you can help historically see problematic servers. So, to collect and view this information in a more useful way, I came up with this example:


get-winevent -FilterHashtable @{logname="system"; providername="Microsoft-Windows-Kernel-General"; ID=1}|select -first 100 -Property TimeCreated,Properties,MachineName | foreach {
     $comp = $_.machinename
     $timeskew = new-timespan -start $_.properties[0].value -end $_.properties[1].value
     $timeskew = [int][math]::abs($timeskew.totalminutes)
     new-object PSObject -property @{
            Machine=$comp
           EventDate = $_.TimeCreated
          TimeDiffMinutes = $timeskew
   }
}|where {$_.TimeDiffMinutes -gt 2}

Here we use Get-WinEvent with a filterhash table to get the events we want. I'm just looking at a limited result here. In each event there are 2 properties which contain the two DateTime values. I'm putting that into a timespan to pull the difference in minutes, removing any negative value and printing out the machinename, Timeskew in minutes and when the change was done. You can add -computer to the initial Get-Winevent to run a list of machines.

Thursday, February 20, 2014

Finding expiring smartcards (or other certificates) on the CA

Recently I was working on a method of discovering and creating alerts for expiring Smartcards.  While looking at some of the various methods to pull details from FIM certificate manager or the AD certificate services CA that issues the certs, I ended up goinig with certutil as the tool of choice for pulling the data.  The build in filtering of the results helped give the ability to confine the results to certain certificate types, and also to avoid anything that was revoked.  Putting this together with output processing in Powershell, it is pretty easy to pull together a list of certs and their expiration time.  Using grouping, you can avoid the problem of having multiple results per user (for those who have already renewed).  Since Smartcards use UPN as an identifier, I went with that attribute to help get the user details.  For other use cases that would need to be modified and the Get-ADUser functionality wouldn't be required.



#this line needs to be modified to target the CA and the OID
#of the cert type that you want to look at (if you are restricting it
#Disposition of 20 means certs that are active
certutil -config "<CA server FQDN>\<CA NAME>" -view -out "user principal name,certificate expiration date" -restrict "certificatetemplate=<templateOID>,Disposition=20"  > .\certdump.txt

$results = @()
$recordbound = $false
Try { $data = get-content .\certdump.txt -erroraction stop } catch { 
  #error handling
 }
For ($i=0; $i -lt $data.count; $i++) {
 #process the multiline record to get user UPN account name and certificate expirations
 if ($recordbound) {
  $result = new-object PSobject
  $UPN = $data[$i].substring($data[$i].indexof('"')+1).trim('"')
  $dateval = $data[$i+1].substring($data[$i+1].indexof(":")+1)
  $dateval = [datetime]$dateval
  add-member -input $result NoteProperty UPN $upn
  add-member -input $result NoteProperty Expiration $dateval
  $i = $i+2
  $recordbound = $false
  $results += $result
 }

 #start of a new multiline record
 if ($data[$i] -match "^Row ") {
  $recordbound = $true
 }
}

#User may have renewed before, so there can be more than one cert...so group by user UPN
$groupdata = $results|group UPN
$curdate = get-date

#Look at all certificates for a given user and select the one with the furthest expiration date (I.e. last one issued to this UPN)
$groupdata = $groupdata | Select Name,@{name="expiration";expression={($_.group|sort expiration |select -last 1).expiration}}

foreach ($entry in $groupdata) {
 $debugstr = "$($entry.name)  Expiration $($entry.expiration)"
 $timediff = new-timespan -start $entry.expiration -end $curdate
 $days = 0 - $timediff.days
 if ($days -lt 30) {
  $debugstr += ".  In expiration window."
  #expiration warning, need to find primary account email and send it, if they are still active
  $user = get-aduser -ldapfilter "(&(objectclass=user)(userprincipalname=$($entry.name)))" -properties enabled,manager
  if ($user.enabled) {
   $debugstr += "  Account is enabled."
   #do something with it
  } else {
   $debugstr += " User 431 is disabled, ignoring."
  }
 }
 write-debug $debugstr
}

Friday, February 14, 2014

New Array, not exactly empty

I have a habit of creating powershell scripts that iniitialize an empty array such as

$a=@()

which I than can use later to add items to it with

$a += $something

Normally for what I do with it, that's fine, but recently I wanted to do a check at the end of the script to see if there were any results:

 if ($a.count -gt 0) {  }

expecting this to be false if nothing was added to the array.  But my script was failing unexpectedly as it seems there is a $null value put in the first position of the array.  So a better way to evaluate in my case is:

if ($a[0] -ne $null) {  }

AD Account Expiration, search results not giving expected values

I recently ran into a problem when trying to find accounts that were incorrectly set for expiration date, especially searching for accounts that were set to never expire.  The attribute in ActiveDirectory is "accountExpires", however when dealing with AD powershell cmdlets such as Get-ADUser, it is filtered as AccountExpirationDate.  Typically someone may assume that if an account is set to never expire, it should not have a value for this attribute as it is not mandatory.  So a search for Null or Empty on that attribute will give you all of the results.  However, I found in the environment that I was working with, many accounts had the attribute set at some point, but the value was still a value that shows in the GUI tools as "never expires".  In this case, the value is one second above given the maximum calendar date. (Value in attribute: December 30, 9999 12:00:00 AM (GMT)).


[datetime]::maxvalue.ToUniversalTime()
&nbsb &nbsb Friday, December 31, 9999 11:59:59 PM

You can get this value by putting it in with the adjusted current time zone, such as this example of US Central Time:

$forever = [datetime]"12/29/9999 6:00:00 PM"

Using the MaxValue function along with the AddSeconds(1) method will fail.


Alternatively, the AD time format of the value is: 9223372036854775807, so you can do an ldap filter such as:
"(&(objectclass=user)(|(accountexpires=9223372036854775807)(!(accountexpires=*))))"


So when you are trying to find accounts that never expire, you may want to filter in two ways:


1) Attribute is null
2) Attribute is equal to the maximum date value

Thursday, February 13, 2014

Creating your own eventlog source name

In the event that you want a script to be able to write to an eventlog, it is often useful to have your own unique Event Source Name to use.  These events can be searched for with various log gathering tools or monitored with SCOM.  If you just try writing an event with any random name using write-event in powershell you may see this error:


write-eventlog -logname system -source MyScript -eventID 1 -message "test" -entrytype Information
Write-EventLog : The source name "MyScript" does not exist on computer "localhost".
At line:1 char:15
+ write-eventlog <<<<  -logname system -source MyScript -eventID 1 -message "test" -entrytype Information
   + CategoryInfo          : InvalidOperation: (:) [Write-EventLog], InvalidOperationException
   + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteEventLogCommand


To get around this, you can easily register your own name

[System.Diagnostics.EventLog]::CreateEventSource("MyScript", "System")

Then try the write-eventlog command again, and it will work fine.