Archive for the 'Scripting' Category

Powershell script to filter events using an Xpath query

I have recently spent some time working with Xpath queries as part of Event Log filtering in Windows Server 2008.  It’s a great feature, but one limitation I found was that it doesn’t appear possible to use the starts-with() function when querying Event Logs with either the UI or Wevtutil.exe.  Here’s an example.

Let’s say I enabled LDAP logging on a DC and want to filter the Directory Service event log to find all LDAP queries coming from a particular IP address.  The IP address is buried in one of the Data nodes of the Event XML, as shown in red below.

<Event xmlns=”http://schemas.microsoft.com/win/2004/08/events/event”>

  <System>

    <Provider Name=”Microsoft-Windows-ActiveDirectory_DomainService” Guid=”{0e8478c5-3605-4e8c-8497-1e730c959516}” EventSourceName=”NTDS General” />

    <EventID Qualifiers=”16384″>1644</EventID>

    <Version>0</Version>

    <Level>4</Level>

    <Task>15</Task>

    <Opcode>0</Opcode>

    <Keywords>0×8080000000000000</Keywords>

    <TimeCreated SystemTime=”2009-04-29T20:39:00.886Z” />

    <EventRecordID>339453</EventRecordID>

    <Correlation />

    <Execution ProcessID=”648″ ThreadID=”792″ />

    <Channel>Directory Service</Channel>

    <Computer>DC1.Myco.Com</Computer>

    <Security UserID=”S-1-5-21-854245398-152049171-725345543-4606″ />

  </System>

  <EventData>

    <Data>CN=MyCo Enterprise Issuing CA 1, CN=Public Key Services,CN=Services,CN=Configuration,DC=MyCo,DC=Com</Data>

    <Data> (objectClass=cRLDistributionPoint) </Data>

    <Data>1</Data>

    <Data>1</Data>

    <Data>192.168.40.10:4048</Data>

    <Data>base</Data>

    <Data>deltaRevocationList</Data>

    <Data>

    </Data>

  </EventData>

</Event>

So if I wanted to use Xpath to filter all events in the Directory Service Event Log from that IP address my query would look something like this:

<QueryList>

  <Query Id=”0″ Path=”Directory Service”>

    <Select Path=”Directory Service”>*[System[(Level=4 or Level=0) and (EventID=1644)]] and *[EventData[Data[5]=’192.168.40.10:4048′]]</Select>

  </Query>

</QueryList>

The query works well, but the problem is that the Data node within the XML contains the port number (4048) in addition to the IP address.  I want to find all queries issued from that client, regardless of the port used.  Here’s my attempt to use the starts-with() function to filter the event.

<QueryList>

  <Query Id=”0″ Path=”Directory Service”>

    <Select Path=”Directory Service”>*[System[(Level=4 or Level=0) and (EventID=1644)]] and *[EventData[starts-with(Data[5],’192.168.40.10′)]]</Select>

  </Query>

</QueryList>

This fails with the error “The specified query is invalid“.  Back to the drawing board.  I posted a question to Technet Forums and got some good help from Ivan Ting at Microsoft.  He provided some Javascript that used starts-with() and this worked (after some fun messing around with default namespace issues).  Being something of a Javascript muppet (the antithesis of a Javascript guru), I decided to try my hand at a Powershell version.  Here’s what I came up with.

#####
# Author: Tony Murray
# File name: LDAPEvents.ps1
# Date: 28th April 2009
# Purpose: Extracts LDAP Search information from Directory Service Event
# Log. Requires LDAP logging to be switched on.
#####

# Function to create an object for XML document navigation
# Source: Technet Scriptcenter
function get-xpn ($text)
{
$rdr = [System.IO.StreamReader] $text
$trdr = [system.io.textreader]$rdr
$xpdoc = [System.XML.XPath.XPathDocument] $trdr
$xpdoc.CreateNavigator()
}

# Run Wevtutil.exe to export the Event log to file
# Could use Powershell to do this but it creates odd-looking xml!

$file = “c:\util\dumplog.xml”
& $env:windir\System32\wevtutil qe `”Directory Service` /e:Events | Out-File $file

# Remove the namespace from the xml file. It won’t work if it stays

$findStr = ” xmlns=`’http://schemas.microsoft.com/win/2004/08/events/event`’”
$ReplStr = “”
$newcontent = (Get-Content $file) -replace ($findStr,$ReplStr)
Set-Content $file $newcontent

# Invoke the navigator
$xb = get-xpn $file

# Define the Xpath query we want to use
$query = “//*[ System[(Level=4 or Level=0) and (EventID=1644)] `
and EventData[starts-with(Data[5],’192.168.40.10′)]]”

# Create a CSV file with the output. Each line represents the details
# we want from a single Event.

Write-Output $xb.Select($query) | %{[xml] $_.OuterXml} | Select-Object `
@{name = “Date&Time”;Expression = {$_.Event.System.TimeCreated.SystemTime}}, `
@{name = “SearchBase”;Expression = {$_.Event.EventData.Data[0]}}, `
@{name = “Filter”;Expression = {$_.Event.EventData.Data[1]}}, `
@{name = “Visited”;Expression = {$_.Event.EventData.Data[2]}}, `
@{name = “Returned”;Expression = {$_.Event.EventData.Data[3]}}, `
#@{name = “SourceIP”;Expression = {$_.Event.EventData.Data[4]}}, `
@{name = “SearchScope”;Expression = {$_.Event.EventData.Data[5]}} `
| export-csv ds.csv -notype
# Replace the previous line with the following line to change the output format
#ConvertTo-HTML | Out-File “LDAPEvent.html”

Having to write a script is more effort than simply issuing the query from within Eventvwr, but it does have the advantage of allowing you to return only the information you are interested in - and in the format that you want.  Hopefully, my experience will save you a bit of time and effort if you are trying to achieve something similar.

Schedule backups of your AD LDS instance using Dsdbutil [2]

In my last post, I provided a small batch file to support scheduled IFM dumps of an AD LDS instance.  Afterwards, I realised that batch files are sooo last century and decided to have a crack at the Powershell version.  I’m no Bwandon, but the script below seems to do the trick.

#
# Name: Create_IFM_Dump.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 20/04/2009
# Comment: PowerShell script to create AD LDS IFM
# backup.  To be run nightly as a scheduled task.
#
#########################################################

# Declare variables$IFMDir = “c:\backup\adlds\Instance1″
$IFMName = “adamntds.dit”
$cmd = $env:SystemRoot + “\system32\dsdbutil.exe”
$flags = “`”ac i Instance1`” ifm `”create full c:\backup\adlds\Instance1`” q q”
$date = get-Date -f “yyyymmdd”
$backupfile = $date + “_adamntds.bak”
$DumpIFM = “{0} {1}” -f $cmd,$Flags

# Main

# Create the folder if it doesn’t exist if(test-path -path $IFMDir)
{write-host “The folder” $IFMDir “already exists”}
else
{New-Item $IFMDir -type directory}
# Clear the IFM folder (Dsdbutil needs folder to be empty before writing to it) Remove-Item $IFMDir\*

# Run Dsdbutil.exe to create the IFM dump file Invoke-expression $DumpIFM# Rename the dump file to give the backup a unique name

rename-item $IFMDir”\”$IFMName -newname $backupfile

# End Main

 Tony

PowerShell GPMC scripts

 

The other day I had a need to configure scheduled backups of GPOs to file on a Windows Server 2008 Domain Controller.  Aha (I thought), I’ve done this before using the BackupAllGPOs.wsf script that is included along with a whole bunch of other handy scripts when you install the Group Policy Management Console (GPMC).  After a few minutes of fruitless searching on my Windows Server 2008 DC I realised that although the GPMC was installed (as a feature) the scripts were nowhere to be found.  After some Googling I found out that I hadn’t been singled out for victimisation - unlike Windows Server 2003, the scripts just aren’t installed by default in Windows Server 2008 when you enable the GPMC feature.  I discovered that you could download the Vista and Windows Server 2008 versions of the scripts here:

Group Policy Management Console Sample Scripts

It puzzled me that the scripts weren’t included by default.  I suspect the Vista and WS2008 versions of the scripts were developed after the products had shipped.  Anyway, it made me think that Microsoft maybe wanted me to work with PowerShell and not VBScript.  Aha (I thought again), I’ll see if I can find the PowerShell equivalent of the GPMC scripts.  After a fair bit of searching I found two options.

Option 1.

SDM GPMC PowerShell Cmdlets from Darren Mar-Elia

Option 2.

Sample functions provided by Thorbjörn Sjövold in his Technet Magazine article, Simplify Group Policy Administration with Windows PowerShell

The first option requires installing the Cmdlets from an .msi install package, something I didn’t really want to have to do in the environment I was working with.

The second option proved a winner and provided the functions I needed to get my PowerShell script up and running within a few minutes.  Here’s my script to backup all the GPOs in a given domain. 

## FileName: BackupGPOs.ps1
## Date: 13.12.2008
## Purpose:  Backs up all GPOs within domain to file

## Variables

$backupDirectory = “c:\backup\GPO”
$domainName = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name

## Functions

# Source: http://technet.microsoft.com/en-us/magazine/cc162355.aspx

###########################################################################
# Function   : BackupAllGpos
# Description: Backs up all GPOs in a Domain
# Parameters : $backupDirectory - The directory where the backups will be stored
#            : $domainName - The dns name, e.g. microsoft.com, of the domain to operate on
#            : $backupComment - An optional comment for the backups, if nothing is passed the current date will be
used.
# Returns    : N/A
###########################################################################
function BackupAllGpos(
  [string] $backupDirectory=$(throw ‘$backupDirectory is required’),
  [string] $domainName=$(throw ‘$domainName is required’),
  [string] $backupComment=$(get-date))
{
  $gpmAllGposInDomain = GetAllGposInDomain $domainName

  foreach ($gpmGpo in $gpmAllGposInDomain) # Iterate through all the GPOs
  {
    “Back up GPO : ” + $gpmGpo.DisplayName
    $gpmResult = $gpmGpo.Backup($backupDirectory, $backupComment) # Backup the GPO
    [void] $gpmResult.OverallStatus
    $gpoBackup = $gpmResult.Result
  }
}

###########################################################################
# Function   : GetAllGposInDomain
# Description: Returns all GPOs in a domain
# Parameters : $domainName - The dns name, e.g. microsoft.com, of the domain to operate on
# Returns    : All Group Policy Objects in the supplied domain
###########################################################################
function GetAllGposInDomain(
  [string] $domainName=$(throw ‘$domainName is required’))
{
  $gpm = New-Object -ComObject GPMgmt.GPM # Create the GPMC Main object
  $gpmConstants = $gpm.GetConstants() # Load the GPMC constants
  $gpmDomain = $gpm.GetDomain($domainName, “”, $gpmConstants.UseAnyDC) # Connect to the domain passed using any DC
  $gpmSearchCriteria = $gpm.CreateSearchCriteria() # Create a search criteria without any restrictions
  $gpmDomain.SearchGPOs($gpmSearchCriteria) # Search and find all GPOs in the domain, this will return the array
}

## Main

backupAllGpos $backupDirectory $domainName

## End

Note that I’ve set the $domainName variable to match the domain of the computer from which the script is run.  To set the variable to match the domain of the user account under which the script runs change it to (may wrap):

[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name

The sample functions provided by Thorbjörn are comprehensive and cover nearly all of the features included in the original GPMC VBScripts.  I encourage you to take a look.

PowerShell ate my C: Drive

Or at least it nearly did.

I was working a script and because of a small typo very nearly lost most of the data on my C: drive.   It’s very easy to get wrong, so treat this as a cautionary tale.   The example below shows the error I made and the potential impact.  I’ve used the command line instead of my script in the example, but the effect is the same.

 First of all I set a variable to point to a folder off the root of the C: drive.  Then I cleared the contents of the folder using the variable together with the Remove-Item command.

 The screenshot below shows how to do this correctly.

powershell1.jpg

 Now look what happens when you mistype the name of the variable.  PowerShell basically ignores the variable and assumes you want to remove everything from the root of the drive. 

 powershell3.jpg

In my case only the fact that I didn’t use the  -recurse parameter saved me.  If I had used the -recurse parameter I would have lost everything except for items protected by the system.

Nasty.

Mixed bag ‘o Nuts

 

It’s been a little while since I’ve blogged, so here’s a more or less random collection of snippets for you to enjoy/delete at leisure.

Quest acquires Netpro

Wow, this one took me by surprise, especially as I have been contracting to Quest on and off for the past 10 months.  Two of the biggest names in the Active Directory management space are now one.  It’s going to take quite a while for competitors to breach the gap.

Microsoft acquires Deano

I just learned from Joe Richard’s blog that Dean Wells has taken a position at Microsoft within the Directory Services product team in Redmond.  I’ve known Dean for the past six years or so and he is one of the most knowledgeable AD people around.  He’s forgotten more about AD than most of us know.  I’m sure he’ll be a huge asset to the DS team.  Good luck Deano!

Handy CSV import script

I came across a good vbscript for modifying AD attribute values using a CSV input file.  There are a number of methods and scripts around that can work with CSV input files, but the cool thing about this script is that can easily be modified to accommodate different attributes.  Check it out here.  I’m thinking of putting together a Powershell version of the same thing.

Good anecdote from Don Hacherl

A while back I blogged about one of the new features of AD in Windows Server 2008: protection from accidental deletion.  If you were looking for a good supporting anecdote to hasten the deployment of this feature in your environment, look no further than this nugget from one of the Godfathers of AD, Don Hacherl, posted on the mailing list at ActiveDir.org:

From: ActiveDir-owner@mail.activedir.org [mailto:ActiveDir-owner@mail.activedir.org] On Behalf Of Don Hacherl
Sent: Sunday, 7 September 2008 4:52 p.m.
To: ActiveDir@mail.activedir.org
Subject: RE: [ActiveDir] Delegating Start/Stop Service on DCs

Years ago I worked with a “domain admin qualified” person at Microsoft who fat fingered the admin UI and deleted a container instead of the object he was intending.  The container was named “North America”, and that was the night we wrote our first authoritative restore tool.  (Later he said “I wondered why it was taking so long to finish.”)

A tightly constrained proxy program can be more reliable and less dangerous than a distracted human administrator.

Don

Tech-Ed

My sessions at Auckland and Sydney completed without mishap and my demos (bizarrely) worked without one single blue-screen :-)  The feedback was positive and I was happy with the eval scores.  Looking at the video of my session, I realise that I need to slow down a little, engage the audience more and stop saying “um” so much.  Talking in front of a large audience is nerve-wracking and I wasn’t even aware I was doing it.

Tech-Ed in Sydney was also a good opportunity to catch up with fellow DS MVP Gil Kirkpatrick and my ex-colleagues from Gen-i, Craig Pringle and James Brombergs.

Stuck without a script Editor? Try MSE7.EXE

 

I sometimes have to work in environments where I have to use the customer’s PC instead of my laptop.  This can be quite frustrating as my laptop is quite heavily customised and has all the tools that I need.  One thing I find problematic is having to work with scripts (especially VBScript) without a decent script editor.  Notepad doesn’t really cut the mustard. :-)

If the PC I am assigned to has Office installed I usually fire up the Microsoft Script Editor (MSE7.EXE) when working with scripts.  This underutilised editor has a sufficiently rich set of features to make working with VBScript a comfortable experience.

The Microsoft Script Editor is normally installed by default with Office 2007, but is not reachable directly from the Start -> All Programs -> Microsoft Office menu.  Instead, you can usually find it by searching for MSE7.EXE on the local drives.  On the machine I am working on now, it is installed in the following location:

%programfiles%\Common Files\Microsoft Shared\OFFICE12\MSE7.EXE

 

mse7

« Previous Page