Windows, Scripting, Virtualization, Cloud Computing - tricks for getting around in the world of Technology

Windows, Scripting, Virtualization, Cloud Computing - tricks for getting around in the world of Technology


  • Category Archives Scripting
  • Tracking vCenter VM and DB

    It has been a while since I managed to do some writing on my blog, mostly because I’ve been busy with other Real Life events, and general lack of time. But now I’m here to share something that has been sitting in my drafts folder for a while. This one is about virtualization.

    2010 and 2011 were virtualization years for me, I worked on several projects in design, implementation, and I learned so much, that looking back I really get a feeling of accomplishment.
    I’ve also been a little “cutting edge”, non conservative with my designs some would say. I guess practice what you preach kind of stuck with me and I made it my mission to build reliable, self contained VMware environments, as much as possible.

    As part of the design process, you always have to think about your management software

    • Where do you put the pieces of software that help you manage the environment?
    • How do you ensure availability and SLA for these components to allow you to recover from failures?

    The answer to the first question can be:

    Option A: In a management cluster, dedicated to management software for the virtualization stack

    • The advantage is you always know where the VMs are, if you have a failure there are 2 servers they can be on.
    • The disadvantage is you dedicate two physical boxes for this purpose, which can have a maximum utilization of around 40% for failover reasons.

    Option B: Next to production machines

    • The advantage is you don’t have to setup a management cluster, and you optimize resource utilization in your datacenter.
    • The disadvantage is that you lose “determinism”, the security of “I know on what server vCenter is sitting, so i don’t have to look for it”, if i get a cluster failure or worse.

    Well I’ve come up with two “tricks” that tackle the drawbacks of the second option, not knowing where your management servers are, making it a preferred choice if your environment does not warrant a dedicated management cluster just for that.

    #1 Track the movement of vCenter and vCenter DB using vCenter Alarms

    This one is a really easy way to keep track of your vCenter components. It works best combined with the second trick you will see below, mainly because it does not cover all scenarios but the advantage of this method is that the information is provided in real time.

    What I am proposing is that you create an alarm in vCenter, that monitors for events that change the VMhost of your vCenter VM. These events are:

    1. VM is being migrated (manually)
    2. VM is being migrated by DRS
    3. VM is being restarted by HA on another host

    The third trigger will be hit and miss, it stands to reason, that if vCenter is not up to send the mail since it being restarted, you may or may not get the email, or you will get it after the fact. nevertheless it is good to have it there.

    Below are the screenshots of how the alarm would look like:

    On the advanced field put this condition in:

    Then add some notification address or whatever you prefer

    Save your alarm, and then try to migrate vCenter and see what happens. You should do this to the vCenter DB server aswell, and any components you feel you should know where they are, for troubleshooting purposes (VUM, Nexus 1000v Supervisor Modules, Management Appliances).

    #2 Check the vSphere host where vCenter is running using a scheduled script

    Another wasy to check where your vCenter components stay is using a scheduled PowerCLI script that runs once a day and sends you an email where vCenter VM and vCenter database are sitting (which vSphere host)

    This script assumes following:

    1. vCenter VM name in inventory = vCenter VM hostname
    2. vCenter is using separate database, if you don’t care about that, you can remove the references to the DB.
    3. vCenter Database name in inventory = vCenter Database hostname or at least a cNAME with this name (e.g. RO-vcenter > RO-vCenter-DB name, and alias in DNS)

    You can customize this by entering a CSV file of the names of the vcenter instances and their respective databases.

     #version 0.1
    #initial release
    
    Add-PSSnapin Vmware.VimAutomation.Core -ErrorAction:SilentlyContinue
    Set-PowerCLIConfiguration -DefaultVIServerMode multiple -Confirm:$false
    #Write-Host -ForegroundColor Yellow "This script Generates a report detailing which host has the vCenter VM and vCenter DB VM`
    #If you wish to cancel Press Ctrl+C,otherwise press Enter"
    #Read-Host
    
    #using fqdn because certificates are issued using a FQDN
    $vCenter = ('vcenter','vcenter2','vcenter3')
    
    If ($global:DefaultVIServers -ne $null) {
    	DisConnect-VIServer * -Force -Confirm:$false }
    $vCenter | % {Connect-VIServer $_ -NotDefault:$false}
    
    $Report = @()
    $vCenters = $global:DefaultVIServers | % {
    	$row = "" | select vCenterInstance,FrontendVMHost,DBVMName,DBVMHost
    	$row.vCenterInstance = $_.Name
    	$row.FrontendVMHost = (get-vm -Name $_.Name.Split(".")[0] -server $_.Name).VMHost
    	#db is hostname + db
    	$dbvm = "$($_.Name.Split(".")[0])DB"
    	$DBVMName = ([System.Net.Dns]::GetHostByName($dbvm)).HostName.Split(".")[0]
    	$row.DBVMName = $DBVMName
    	$row.DBVMHost = (get-vm -Name $DBVMName* -server $_.Name).VMHost
    	$Report += $row
    }
    
    $FileDate = get-date -Uformat "%Y%m%d-%H%M%S"
    $Path = "c:\temp\vsphere\"
    $File = "$FileDate-vCenter-InfraLocation.csv"
    $Report | Export-Csv -NoTypeInformation -UseCulture "$Path$File"
    
    $encoding = [System.Text.Encoding]::UTF8
    #I made the convoluted out-string construct because the object cannot be serialized"
    $ReportBody = $null
    $ReportBody += $Report | % { "
    `n
    `n$($_.vCenterInstance)`n$($_.FrontendVMHost)`n$($_.DBVMName)`n$($_.DBVMHost)"}$Body = "</pre>
    <div>I'm the PowerCLI Magic Script. This is the list of your vCenter instances and their locations in the Infrastructure.
    `
    If you ever lose track of them, this email is the reminder. The latest update is from $FileDate
    `
    Below is the detailed information about each Instance:
    `
    `n`n`n`n`n`n$ReportBody`n`n
    <table border="`&quot;1`&quot;">
    <tbody>
    <tr>
    <td>vCenterInstance</td>
    <td>FrontendVMHost</td>
    <td>DBVMName</td>
    <td>DBVMHost</td>
    </tr>
    </tbody>
    </table>
    </div>
    <div>"
    
    Send-MailMessage -Smtpserver smtpserver -From 'admin_vmware@foo.com' -To 'vSphereAdministrators@foo.com' -Body $Body -Bodyashtml -Encoding $encoding -Subject "vCenter Instances List" -Attachments $Path$File

    Learning points:

    Line 11: This is where you define you vCenter server names, if you have more of them. I had 3 for example.

    Line 22 & 27: This is where you perform a get-vm to find out the host where this VM is residing on

    The rest of the script is just to cycle through all vCenter instances and create an email that it sends to a given email address.

    Perhaps to some people this may seem unnecessary, as they may not have faced major outages, perhaps to some it may seem that these monitoring tricks are not enough to cover monitoring of all ‘outages’ situations, but I find it is not worse than having a separate management cluster, with the added benefit of not having to deal with another separate management cluster.

    C&C as always is welcome

    Share

  • Get List of Installed Windows Updates

    This post falls into the category of note to self posts. A while back I researched the Internet for a way to get the list of installed updates on a computer.
    This is useful for those of us still using Microsoft WSUS without SCCM or some other Reporting Tool, because WSUS reports only the number of computers having or not having a patch installed/applicable, but not which ones.
    As of this date there are no Powershell cmdlets that let you get this information, no WMI query no nothing. You have to get it programatically, so I went along and created the following powershell code that creates a report.

    $InputObject = Read-host -Prompt "Insert Computername to get list of installed updates";
    $Report = @()
    $filename = "$env:Temp\Report_$(get-date -Uformat "%Y%m%d-%H%M%S").csv"
    $InputObject | % {
       $objSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$_))
       $objSearcher= $objSession.CreateUpdateSearcher()
       $HistoryCount = objSearcher.GetTotalHistoryCount
       $colSucessHistory = $objSearcher.QueryHistory(0, $HistoryCount)
       Foreach($objEntry in $colSucessHistory | where {$_.ResultCode -eq '2'}) {
           $pso = &quot;&quot; | select Computer,Title,Date
           $pso.Title = $objEntry.Title
           $pso.Date = $objEntry.Date
           $pso.computer = $_
           $Report += $pso
           }
       $objSession = $null
       }
    }
    $Report | where { $_.Title -notlike 'Definition Update*'} | Export-Csv $filename -NoTypeInformation -UseCulture
    ii $filename

    Once you run this report and have an csv viewer installed (excel for example( it will open up the file so you can review it. When exporting I did a filter to remove MS Forefront definition updates as it is pretty irrelevant most of the time, you use other tools to manage Forefront definitions.

    Learning Points

    Line 05 – This line creates and instance of the Windows Update API. What is neat about this function is the fact that can create an instance of the API and connect to a remote computer, notice the “$_” at the end of the line.

    Line 09 – In this line after searching the entire history we filter out all but successful updates. Yes it would be nice to do that in the actual search, but I don’t know if it is possible. So I resorted to filtering out only successful result codes.

    Below is a table with possible values. This can be useful if you want to generate a report based on the result code

    Result Code Update Status
    0 Not Started
    1 In Progress
    2 Successful
    3 Incomplete
    4 Failed
    5 Aborted
     

    That’s about it with getting the list of installed updates, the bit of code above can be easily integrated to run across a large number of computers. Thanks for reading and feedback.

    Share

  • Dismount Recovery Storage Group using Exchange 2007 Shell

    Dismounting the RSG from and Exchange Mailbox server is the last step after you have recovered data you needed. I would like to stress here that my experience tells me this is the step when you will encounter issues, not necesarily issues with the process itself but mostly with the GUI interface. I’ve spent a lot of time trying to figure out which a RSG won’t dismount, when the GUI reported success. So I have come up with a script that does this using exchange management shell, which gives you a little more control and additional debugging information.

    Here is what you will need to accomplish this:

    • Exchange Server Administrator Rights where the RSGs are located.
    • Exchange Management Shell since this entire procedure is best done using powershell scripting.
    • Run the script as Administrator to avoid errors due to enabled UAC

    The Script

    #Dismount DB, Remove, DB, Remove Storage Group
    $DB_in_RSG = Get-MailboxDatabase -Server <ENTERSERVER> | where {$_.Recovery -like '*true*'}
    Write-Host -ForegroundColor Green "The Database in the RSG ($($DB_in_RSG.Name)) will be Dismounted. Press Ctrl+C to cancel or Enter to continue"
    Read-Host
    $DB_in_RSG | Dismount-Database -Verbose -Debug
    
    Write-Host -ForegroundColor Green "The Database in the RSG will be Removed. Press Ctrl+C to cancel or Enter to continue"
    Read-Host
    $DB_in_RSG | Remove-MailboxDatabase -Verbose -Debug
    
    #Now deleting the files of the database
    $SG_Path = (Get-StorageGroup $DB_in_RSG.StorageGroup).SystemFolderPath
    Write-Host -ForegroundColor Green "The actual DB files will be removed (stored in $SG_Path). Please check previous steps completed successfully before continuing. Press Ctrl+C to cancel or Enter to continue."
    Read-Host
    get-item $SG_Path | del -Force -Recurse -Verbose -Debug
    
    #Now removing storage group
    Write-Host -ForegroundColor Green "The RSG will be Removed. Press Ctrl+C to cancel or Enter to continue"
    Read-Host
    Remove-StorageGroup -Identity $DB_in_RSG.StorageGroup -Verbose –Debug

    Learning Points

    There are a few steps you need to do:

    1. Grab the RSG (there is only one per server) – stored in the script in $DB_in_RSG
    2. You dismount the database from the server using Dismount-Database cmdlet.
    3. After disomunting the database you remove the Database using Remove-MailboxDatabase cmdlet. This will just remove the DB from Exchange, the files will remain on the file system.
    4. The actual files on the file system get removed using the path from variable $SG_Path. Use del cmdlet with -force -recurse to bypass any confirmation prompts.
    5. The last step is removing the Recovery Storage Group from the Exchange. The commandlet for this is Remove-StorageGroup.

    That’s about it, pretty easy actually. Although this script is the last in the series I reccomend you actually run this before anything, before mounting the RSG or at least do a check to see if a RSG is already mounted.

    Share

  • Restore a mailbox in the Recovery Storage Group

    Finally I managed to find some more time about posting the next bits of the Exchange mailbox recovery process. Last post we discussed how to mount the database into the RSG. This time we will be discussing how to restore a user’s mailbox in the original mailbox location and how to restore the mailbox data to another temporary mailbox and exporting that out of the temporary mailbox.

    Here is what you will need to accomplish this:

    • Exchange Server Administrator Rights where the mailbox(es) are located.
    • Full mailbox access for exporting emails out of the temp mailbox I was talking about
    • Exchange Management Shell since this entire procedure is best done using powershell scripting.
    • Obviously free space both on the Database disk aswell as disk space where the PST file will be saved.

    The Script

    #Parameters Section
    param (
     [parameter(Mandatory = $true)]
     [string]$paramRestoredUser
    )
    
    cls
    #Restore mailbox section
    $Filter = "SamAccountName -like '$paramRestoredUser'"
    $SourceAlias = Get-Mailbox -Filter $Filter -IgnoreDefaultScope
    
    If ($SourceAlias -eq $null) {
     Write-Host -foregroundcolor Red "No Mailbox for SamAccountName $paramRestoredUser found.Script will quit"
    exit
     }
    
    $RSG_DB = Get-MailboxDatabase -Server $SourceAlias.ServerName | where {$_.Recovery -like '*true*'}
    if ($RSG_DB -eq $null) {
     Write-Host -ForegroundColor Red "No RSG was found on $($SourceAlias.ServerName). Script will quit now!"
     exit }
    
    $TargetAlias = Get-Mailbox e2k7_Restore_MBX
    Write-Host -ForegroundColor Green "Input the date of the restored mailbox:"
    $RestoreDate = Read-Host
    
    $TargetFolder = "$($SourceAlias)_$RestoreDate"
    Restore-Mailbox -Identity $TargetAlias.Alias -RSGMailbox $SourceAlias.ExchangeGuid -RSGDatabase $RSG_DB -TargetFolder $TargetFolder -BadItemLimit 1000 -Verbose -Debug -Confirm -ValidateOnly
    
    echo "-ValidateOnly switch from the command above is removed. Please check no errors occured above and everything is configured properly. Press Enter to continue CTRL+C to cancel the script!"
    Read-Host
    Restore-Mailbox -Identity $TargetAlias.Alias -RSGMailbox $SourceAlias.ExchangeGuid -RSGDatabase $RSG_DB -TargetFolder $TargetFolder -BadItemLimit 1000 -Verbose -Debug -Confirm
    
    #building an excluded folders list, just in case we did another restore and the previous content was not deleted
    $IncludedFolders = $TargetFolder
    $ExclFoldersList = @()
    $ExcludedFolders = Get-MailboxFolderStatistics $TargetAlias | where-object { $_.FolderPath -notlike "*$($IncludedFolders)*"} | Select-Object FolderPath | foreach-object {
     $ExclFoldersList += $_.FolderPath }
    $ExclFoldersList = ([string]::join(",",$ExclFoldersList)).Replace("/","\")
    
    $UserName = $TargetAlias
    $PSTPath = "Q:\UserPersonalFolders"
    "Excluded folders list: $ExclFoldersList"
    Read-Host
    export-Mailbox -Identity $UserName.SamAccountName -BadItemLimit 1000 -DeleteContent $True -PSTFolderPath $PSTPath -ExcludeFolders $ExclFoldersList

    The details for the Script

    First the script gathers data about the restored mailbox, and from that info the Recovery Storage Group ($SourceAlias). It also needs a target mailbox, where the restored data will be stored ($TargetAlias) (needless to say I did in a “forest friendly way, using -Filter cmdlet looking up the given SamAccountName). The data will be moved from the mailbox in the RSG into the $TargetAlias mailbox. It will be placed in a folder, named with the date of the restored data ($RestoreDate). Restoring the data is done with this command:

    Restore-Mailbox -Identity $TargetAlias.Alias -RSGMailbox $SourceAlias.ExchangeGuid -RSGDatabase $RSG_DB -TargetFolder $TargetFolder -BadItemLimit 1000 -Verbose -Debug -Confirm -ValidateOnly

    Now we want to export the data from the $TargetAlias into a PST file. This is easy to do via the Export-Mailbox cmdlet. The problem appears when you do for instance multiple exports in the same target mailbox and then a mass export, or just have left old data in the target mailbox. You do get data separated into folders, if your $TargetFolder name is unique, but the Export-Mailbox cmdlet cannot export data based on this information (it can only do some filtering (date,content,subject) and I believe it is resource intensive). What Export-Mailbox does have is the “-Excludefolders” parameter which lets you not export certain data.

    The trick I came up with was to scan the $TargetAlias mailbox for foldernames (I used Get-MailboxFolderStatistics for that) and build a list of the folders that did not contain the value I entered in the $TargetFolder variable upon restore. Besides that you also have to do some parsing of the output from get mailboxfolderstatistics into something Export-Mailbox understands. This is what I did:

    $IncludedFolders = $TargetFolder
    $ExclFoldersList = @()
    $ExcludedFolders = Get-MailboxFolderStatistics $TargetAlias | where-object { $_.FolderPath -notlike "*$($IncludedFolders)*"} | Select-Object FolderPath | foreach-object {
     $ExclFoldersList += $_.FolderPath }
    $ExclFoldersList = ([string]::join(",",$ExclFoldersList)).Replace("/","\")
    downtime romtelecom

    In the end I just ran the Export-Mailbox on the target mailbox, specifying the excluded files and that was that.

    The whole process is pretty easy, the actual restore is done in a one liner (Restore-Mailbox), but as you can see error checking and failproofing the script make up the rest of the work. Hope you enjoy this, next up is how to dismount a RSG, the scripted “clean-way”.

    Share

  • Restoring mailboxes in Exchange 2007 (part 1)

    Posted on by Ionut Nica

    Lately I’ve been doing a number of mailbox restore procedures on Exchange 2007, so I thought it would be a good idea to make my own posts about it (yes it involves scripting), because things are not always as straightforward as MS or TechNet say it is. This is going to be a multi-part post: Create the RSG and mount the DB to be restored, Restore mailbox(es), Remove the restored DB and RSG. Before you think about it I’m going to answer it for you:

    Q: But why don’t we use the nice GUI Tool from Exchange Management Console (Extra.exe) and do it from there, “we don’t need no scripting”?

    A: My experience tells me the scripted method is safer and works “as expected” unlike the GUI, which says it did something, when it didn’t (I’ve spent days trying to figure out why a RSG Database won’t actually dismount when the GUI said: “Completed Successfully”.

    OK, let’s get on with it. All that I am about to explain requires Exchange Administrator privileges on the Exchange servers.

    We will be creating a Recovery Storage Group, this is the first step in the restore process. To create the RSG you need following:

    • Adequate disk-space to restore the mailbox database, locally on the Exchange Server where the DB was residing
    • Exchange Management Shell running as Administrator (especially on CCR clusters)
    • No other Recovery Storage Group already created on that server with an existing RSG database (you can only have 1 RSG with 1 DB in the RSG). It is best to remove any previous RSG completely then recreate it for your needs.
    • Specific information like which DB to link to the RSG and the list of mailboxes to restore.

    Creating a Recovery Storage Group can be as easy as this:

    New-StorageGroup -Server <MBX Role Server Name> -Name <StorageGroup Name> -LogFolderPath <Logs Folder> -SystemFolderPath <SystemFiles Path> -Recovery -Verbose

    The command is very similar to creating a new SG, except for the -Recovery switch, designating it as a Recovery Storage Group. I added the -Verbose switch so you can see what is going on behind the scenes.

    New-MailboxDatabase -MailboxDatabaseToRecover <Mailbox Name> -StorageGroup <Recovery Storage Group Name> -EdbFilePath <path to store edb file> -Verbose

    Here it is just as easy as creating a new mailbox database, only you are creating it in the recovery storage group you created with the previous command. The key thing to remember here is that the value of the “MailboxDatabaseToRecoverParameter” must be the exact same name of the mailboxDB of which you want to recover from. If the name is different you will not be able to run any restore commands, because it will not be able to find any mailboxes when it searches the recovered database.

    A working script for creating the RSG

    Below I’m sharing with you a working snippet that should help in creating a recovery storage group and DB. In short here is what the code does:

    Using a given UserPrincipalName…

    • Attempts to retrieve the mailbox for the UPN (it is a “forest friendly” coding for retrieving the mailbox). If it fails it quits
    • Checks if a folder structure for placing, logs, system files and the edb file exists (I used a location called d:, use a variable if you like).
    • If folders already exist, it will quit, otherwise it will create a folder with the MDB name, and logs and edb subfolders,
    • Next it checks if a Recovery Storage Group already exists, unless you cancel the script it will continue to use this RSG, with the given details. Otherwise it will create a RSG on its own.
    • It will then create a mailbox database where you / your backup admin will restore your exchange backup.
    $MBX_UPN = Read-Host
    $Filter = "UserPrincipalName -like '$MBX_UPN'"
    $SourceMBX =  get-mailbox -IgnoreDefaultScope -Filter $Filter
    If ($SourceMBX -eq $null) {
    	Write-Host -foregroundcolor Red "No Mailbox for $MBX_ID found`nScript will Quit"
    	exit }
    Write-Host -ForegroundColor Green "Source Mailbox is`n $SourceMBX"
    
    $LinkedMDB = Get-MailboxDatabase -Identity $SourceMBX.Database
    Write-Host -ForegroundColor Green "Ok, Database ($($LinkedMDB.StorageGroup.Name)) is grabbed, now creating RSG Folders and RSG`nPress Enter to continue or Ctrl+C to Cancel"
    Read-Host
    
    #Checking if the RSG folders already exist, if not attempt to create them
    If ((Test-Path "d:\$($LinkedMDB.StorageGroup.Name)")) {
     Write-Host -ForegroundColor Red "Folder already exists. Please remove d:\$($LinkedMDB.StorageGroup.Name) before running this script again.`nScript will quit"
     exit
     }
    $SysPath = New-Item -Type Directory -Path d: -Name $LinkedMDB.StorageGroup.Name | Get-Item
    If ((Test-path $SysPath)) {
     $DBPath = New-Item -Type Directory -Path $SysPath -Name DB | Get-Item
     $LogsPath = New-Item -Type Directory -Path $SysPath -Name Logs | Get-Item
     }
    #If folders were created successfully we can continue
    If ((Test-path $SysPath) -and (Test-path $DBPath) -and (Test-path $LogsPath)) {
     #Checking if RSG already exists
    $RSG_check = Get-StorageGroup -Server $LinkedMDB.ServerName | where {$_.Recovery -like "True"}
     If ($RSG_check -ne $null) {
     Write-Host -ForegroundColor Magenta "A RSG was found on $($RSG_check.ServerName). Here are RSG Details:"
     $RSG_Check | select-object Name,Identity,Recovery,LogFolderPath,SystemFolderPath | fl
     Write-Host -ForegroundColor Magenta "To use this RSG Press Enter, to cancel Press Ctrl+C"
     Read-Host
     }
     Else {
     Write-Host -ForegroundColor Green "Now creating Recovery Storage Group..."
     New-StorageGroup -Server $LinkedMDB.Server -Name "Recovery Storage Group" -LogFolderPath $LogsPath.FullName -SystemFolderPath $SysPath.FullName -Recovery -Verbose
     }
     Write-Host -ForegroundColor Green "OK! No RSG found. Now creating RSG Database..."
     New-MailboxDatabase -MailboxDatabaseToRecover $LinkedMDB.AdminDisplayName -StorageGroup "$($LinkedMDB.ServerName)\Recovery Storage Group" -EdbFilePath "$($DBPath.FullName)\$($LinkedMDB.Name).edb" -Verbose
     }
    Else {
     Write-Host -ForegroundColor Red "Could not Create folder or folder structure in d:\$($LinkedMDB.StorageGroup.Name). Check messages above for errors! Script will quit."
     exit
     }

    This is about it with creating a Recovery Storage Group, it is actually not difficult, just remember to name the MDB inside the RSG with the same name as the source MDB (this was also required on Exchange 2003, as far as I know). Also you cannot have more than one RSG per Maibox Server, it is best to remove any RSG you have after you are finished recovering data. Next post we will discuss how to restore data from a MDB and how to remove the RSG.

    As always I value your feedback and hope you found this post useful.

    Share

  • Log Battery and Power Levels using Powershell

    Posted on by Ionut Nica

    This is a let’s say lighter post, I came up while trying to compare battery life of my laptop and some buddies of mine. I wanted to know, how fast my battery depleted using different settings, use profile and power saving modes. Then I did some digging around Microsoft’s MSDN site, and I found some interesting WMI classes, that apparently provide a lot of “power related data”. I also wanted to have a way to log this data, and that’s how I ended up learning how to create a new event-log file and write data to it to use that as a log. So this is what I will try to show: get power related data and write it to the Event-Log.

    “Energy” Related WMI Classes

    Here are a few interesting classes I stumbled upon. Some of them are only available under Windows7 probably also Vista, but I’m not sure.

    • WmiMonitorBrightness – gives information about monitor brightness. For example these line give the max. “value” and current value of brightness
    $MaxBrightness = get-wmiobject -class WmiMonitorBrightness -Namespace root/wmi).level | measure-object -Maximum).maximum
    $CrtBrightness = "{0:P0}" -f ((get-wmiobject -class WmiMonitorBrightness -Namespace root/wmi).CurrentBrightness/$MaxBrightness)
    • Win32_PowerPlan – provides information and identifiers about the powerplans defined. In this class ALL powerplans are defined, and just the active plan has an “IsActive” flag attached it, here’s how to get it:
    $powerplan = (Get-WmiObject -Class win32_powerplan -Namespace 'root/cimv2/power' | where {$_.IsActive -eq $true}).ElementName
    • Win32_Processor – gets information about the CPU (I was interested in the CPU load for statistical purposes). This one was pretty easy to find, the value was written in plain sight. Take a look:
    $cpu = (Get-WmiObject Win32_Processor).LoadPercentage
    • Win32_Battery – Provides information about the battery itself (estimated time, remaining load, power status). Running “Get-WmiObject -Class Win32_Battery | gm” take a closer look at these members:
      • BatteryStatus – this will toggle between ’1′ meaning on Battery and ’2′ meaning on AC Power
      • EstimatedRuntime – this will be the number of minutes running on battery, as the OS estimates it, and if you get a very high value (tens of thousands) when you plug the AC Power, it means the battery is charging
      • EstimatedChargeRemaining – percentage-wise representation of battery charge remaining

    Powershell + Event-Log “101″

    I used this battery and power experiment to learn more about working and writing data to the Event-Log. I wanted to create a new “Event-Log” in Windows (windows 7 as you probably know allows for a lot of application logs) and then write events to it. Then at any point you can export the Event-log to csv. The following creates an Event-Log, with the name “BatteryMonitor” from the Information category (for my uses “Source” was not needed but it is a required parameter:

    New-EventLog -Source BattMon -LogName BatteryMonitor -CategoryResourceFile Information

    You can also check if an Event-log is created exists you can use this scriptlet (the answer lies in WMI this time, I didn’t find a cmdlet that does it faster):

    (get-wmiobject -class "Win32_NTEventlogFile" | where {$_.LogFileName -like 'BatteryMonitor'} | measure-object ).count -eq '1'

    Finally here’s how to write to the event-log, a new event. This bit I used in a script to mark the execution of the script in the event-log:

    Write-EventLog -LogName BatteryMonitor -Source BattMon -EventID 65533 -Message 'Starting new Execution of BatteryCharge Monitor Script. The script will pump here CSV values. Values are listed in this order, as CSV: PowerPlan,PowStatMsg,ChargeRemMsg,RemTimeMsg,RAM,CPU,CrtBrightness' -EntryType Information -ComputerName $env:computername -ErrorAction:SilentlyContinue

    So that is about it, as usual I tried to tie all of these scriptlets into a usable script, you can download it from here.

    Share

  • Restrict USB Storage Devices on Windows XP

    Posted on by Ionut Nica

    This is one of those topics that are probably on the top 10 to do’s of anyone’s list when it comes to securing their Windows desktops. Whether it is plain dictatorship, security/confidentiality concerns/requirements, unpatched OS’s, weak/no AV solutions, the golden POLP (Principle Of Least Privileges) may force you to come up with a solution to this problem. If you are using anything else (XP, 2000, 2003 Server) except the newer versions of Windows (Vista, 7) which allow you to do this via a GPO setting, you are out of luck, there is no GPO setting or quick-fix that works.

    As a short history, I went through CIA documents that were published (can’t find them anymore), Forums, Microsoft KB’s, Whitepapers, and finally came up at the other end of the tunnel with a working process.

    The goal is to devise a process of denying access to USB Storage that meets following criteria:

    • Must be implemented at OS level
    • Must be deployed scripted/automatically and/or via GPO
    • Must not cripple other OS functionality (e.g. installing printer/scanner drivers)
    • Must be fully reversible by Administrators only
    • Must be working regardless if USB Storage was used before the process is put in place

    The solution – explained

    For disabling USB Storage there are 2 situations to cover:

    • No USB storage ever installed, user must not be able to install device
    • USB storage was previously installed by user or admin, user must not be able to use USB Storage again

    Both scenarios are covered in these 6 steps:

    1. Copy usbstor.inf, usbstor.pnf, usbstor.sys to their default locations, as if a USB storage device would be installed.
    2. Restrict access to the 3 files mentioned above. We will use an implicit DENY for the local “SYSTEM” Account for these files.
    3. Remove Registry Keys that handle USB Storage device startup: HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR and HKLM\SYSTEM\ControlSet001\Services\USBSTOR and HKLM\SYSTEM\ControlSet002\Services\USBSTOR
    4. Replace USB Storage related registry keys with specially crafted keys that disable startup of the USB Storage driver
    5. Apply an implicit DENY for the local SYSTEM Account on the Registry Keys mentioned above
    6. Insert USB Storage device, wait for it to be detected by OS and marvel at the fact it won’t let you install the device :( :)

    For enabling USB Storage these steps must be taken from an Account that is member of the Administrators Group

    1. Remove restrictions placed on the ubstor.* files.
    2. Remove following specially crafted Registry Keys: HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR and HKLM\SYSTEM\ControlSet001\Services\USBSTOR and HKLM\SYSTEM\ControlSet002\Services\USBSTOR
    3. Remove restrictions placed on the registry keys from above
    4. Delete incompletely installed USB storage devices fron Device Manager and Reboot Computer
    5. Insert USB Storage device, wait for it to be detected by OS/go to device manager and refresh device list and marvel at the fact it works :)

    Implementation – explained

    For implementing this in a scripted manner we will use batch scripting, I’m going for a low level approach, assuming you don’t have vbs / powershell on hand, vbs would be rather complicated anyway and Powershell is not installed by default on the OS. You do have some prerequisites:

    • reg.exe (available by default on XP)
    • A network share
    • set-acl (open source utility – get it, copy to a network share of choice and be happy it exists)

    Disabling USB

    • The 3 usbstor files mentioned earlier, 2 are available by default (usbstor.inf and usbstor.pnf) under %WINDIR%\inf. The 3rd, usbstor.sys, unless a usb storage device was previously installed is not present. Find it under %WINDIR%\Driver Cache\i386\Sp3.cab or the other cab files there. Extract it from the cab file to the network share.
    • The piece of code that disables USB is written below, but requires that set-acl, the specified .txt, .reg, usbstor.sys files be present in the same directory from which it is executed
    ::Copy ubstor.sys
    xcopy /R /H /Y %CD%\usbstor.sys %windir%\system32\drivers
    
    ::Secure USBSTOR.* files with ACE (only Local Administrators Full Control, local "SYSTEM" denied Full Control)
    SetACL.exe -on "c:\windows" -ot file -actn restore -bckp "%CD%\usbstor_ACL.txt"
    
    ::Delete settings related to USBSTOR Service
    REG DELETE HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR /f
    REG DELETE HKLM\SYSTEM\ControlSet001\Services\USBSTOR /f
    REG DELETE HKLM\SYSTEM\ControlSet002\Services\USBSTOR /f
    
    ::Add special crafted registry keys
    regedit /s "%CD%\disable_usb.reg"
    
    ::Secure keys from above with ACE (only Local Administrators Full Control, local "SYSTEM" denied Full Control)
    SetACL.exe -on "hklm\SYSTEM" -ot reg -actn restore -bckp "%CD%\HKLM_ControlSet.txt"
    • Line 5 of the code uses a file that contains a specially formatted ACL applicable to the 3 usbstor files. To generate a different ACL, use the syntax below for each file you are interested in. When you are finished you can merge all text files in a single text file and add it to the script.
    SetACL.exe -on "c:\windows\inf\usbstor.inf" -ot file -actn list -lst "f:sddl;w:d,s,o,g;s:b" -bckp "%CD%\usbstor_inf_ACL.txt"
    • REG command is used to delete any data that may exist in the specified registry keys (think previous installed USB Storage)
    • Once the Registry is clean of the keys, we then push a customized reg file (find it at the end of the post), that essentially changes this:

    USBSTOR driver points to the file you defined (usbstor.sys, that you just set a restrictive ACL on)

    DeviceCount equals zero :)

    DeviceStartUp Type is set to Disabled (more details here)

    Other standard settings for that key

    • Line 16 of code, similar to the ACL for USBSTOR Files, configures the security for the registry keys we added. To customize the ACL, change it to your liking then export the ACL using the command below and update the batch code to include it.
    SetACL.exe -on "hklm\SYSTEM\CurrentControlSet\Services\usbstor" -ot reg -actn list -lst "f:sddl;w:d,s,o,g;s:b" -bckp "%CD%\HKLM_CurrentControlSet.txt"

    Enabling USB

    This is just a question of reversing the changes made by the Disabling process. The following piece of code does just that:

    ::enable inheritance of permissions
    SetACL.exe -on "c:\windows\inf\usbstor.inf" -ot file -actn setprot -op "DACL:np;SACL:np"
    SetACL.exe -on "c:\windows\inf\usbstor.pnf" -ot file -actn setprot -op "DACL:np;SACL:np"
    SetACL.exe -on "c:\windows\system32\drivers\usbstor.sys" -ot file -actn setprot -op "DACL:np;SACL:np"
    
    ::clear any non-inherited ACE
    SetACL.exe -on "c:\windows\inf\usbstor.inf" -ot file -actn clear -clr "dacl,sacl"
    SetACL.exe -on "c:\windows\inf\usbstor.pnf" -ot file -actn clear -clr "dacl,sacl"
    SetACL.exe -on "c:\windows\system32\drivers\usbstor.sys" -ot file -actn clear -clr "dacl,sacl"
    
    ::deleting custom Registry Keys
    REG DELETE HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR /f
    REG DELETE HKLM\SYSTEM\ControlSet001\Services\USBSTOR /f
    REG DELETE HKLM\SYSTEM\ControlSet002\Services\USBSTOR /f
    • As you can see we are enabling inheritance of permissions, clearing any ACE defined explicitly on that object (the ones we pushed actually) and removing the Registry keys we also pushed. Make sure the user running this enabling process has rights to change these objects (in our case he is member of the Local Administrators Group)
    • After this is done manually clean it of any hidden installed USB Storage devices and reboot the computer. After the reboot replugging the device should allow you to install and use it again.

    Phew, this was also a long post, but believe me, reaching this compressed format was a lot of work :) .

    Now I’ve attached this zip file containing the contents of what I’ve been talking about, it should be usable out of the box.

    There is also there question I guess of securing these files so that they apply to users but users can’t get to them to “help themselves”, but that is another topics for another post perhaps.

    As always any feedback is welcomed.

    Share

  • Configure USB device to boot WindowsPE

    Posted on by Ionut Nica

    It’s been a while since my last post but now I found some time to write a complimentary article to my “make your PE boot disk series”. While the make your PE disk series showed you how to make a bootable ISO, it is possible however to write the contents of the ISO to a USB stick and by following this guide to boot Windows PE from USB. The advantage of having a WinPE on USB is great flexibility, with a CD you always have to rebuild the ISO and burn it/mount it whenever you feel like adding new applications/scripts to it. With a flash drive you can just copy the data alongside the booting OS.

    Here’s what you need:

    • USB stick of choice (portable USB HDD works aswell)
    • A PC running at least a Windows Vista kernel (Windows 7, 2008 – WinPE versions of these also work)
    • Administrator privileges on the machine.

    With the introduction of Vista and later kernels making USB storage bootable for windows became so much easier. Before (win Xp/2003 days there were specialized tools that major hardware vendors had, as far as I know). Why this is was not working in XP/2003?

    The reason is that 5.x Versions of Windows did not mark a USB device as hard disk storage device, and you could not use “diskpart” command line utility, to mark partitions as active, what you need to get WinPE to boot.

    Diskpart is a pretty powerful disk partitioning utility that comes free with Windows, can be used to manage hard disks when you lack a GUI or are a scripthead like me :) . What is great about it is that it’s also included in WindowsPE which can make WinPE a one stop shop for disk management up to a certain point.

    The steps we will go through are:

    1. Use Diskpart to list all system disks and select our USB stick
    2. Partition USB stick as desired and mark a specific partition as active
    3. Copy WindowsPE files to the USB stick

    Select system drive for WinPE boot

    First step is open up your command prompt running it as Administrator. Type diskpart at the prompt and hit enter. After a few seconds you get a command prompt headed by “DISKPART>”.

    At the prompt type list disk - this will list all available physical drives. This next step is CRUCIAL. Pay extremely good attention to the information shown. The command will list the disks on your system. Identify which on the disks listed there is your USB stick, use the “Size” parameter to figure it out. If you are still not sure what type of disk you have selected do following:

    select disk [enter #] <<enter a disk number from the listing you did>>
    detail disk

    The output should say your select disk is TYPE:USB. If you get TYPE:ATA, list the disks again and select another number!

    DISKPART> detail disk
    
    <DEVICE NAME>
    Disk ID: 2EA32EA2
    Type   : USB
    Status : Online
    Path   : 0
    Target : 0
    LUN ID : 0
    Location Path : UNAVAILABLE
    Current Read-only State : No
    Read-only  : No
    Boot Disk  : No
    Pagefile Disk  : No
    Hibernation File Disk  : No
    Crashdump Disk  : No
    Clustered Disk  : No
    
     Volume ###  Ltr  Label        Fs     Type        Size     Status     Info
     ----------  ---  -----------  -----  ----------  -------  ---------  --------
     Volume 6     G                NTFS   Removable   7679 MB  Healthy

    Repeat the “select disk #” and “detail disk” until you find your USB device that you wish to make bootable. To see which disk you selected run:

    DISKPART> list disk
    
     Disk ###  Status         Size     Free     Dyn  Gpt
     --------  -------------  -------  -------  ---  ---
     Disk 0    Online          186 GB      0 B
    * Disk 1    Online         7680 MB      0 B

    Up to this point we’ve done nothing to the usb device, but I hope you have a backup /don’t care about the data on, because it will be gone in the next step…

    Prepare disk and mark as active

    With the proper selected disk we will wipe all partitioning data from it, create a single partition, format it as NTFS, give it a drive letter, and mark the partition as active.

    !WARNING!: The following will wipe your device, so make sure the selected disk it is the correct one. (use list disk and look for the “*” to see which disk is selected)

    clean
    create partition primary
    format fs=NTFS quick
    assign letter=U
    active
    exit

    All of these commands will echo a response that they ran successfully, once done type exit to leave the diskpart context and let’s copy the WinPE files to disk.

    Copy Windows PE Files

    Ok, time for a little linking to my previous posts (post1 and post2). In these posts I discussed how to make a Windows PE boot Disk. IF you followed that tutorial (or similar ones on the internet) you will probably be stuck with a folder called ISO in the <PATH>\WinPE_x86\ folder. Also you should left with the ISO file. BOTH of them are good for this next step which is “Copy all the files from either the ISO folder or the ISO IMAGE you built to the root of the USB partition” (make sure you copy all files and folders including hidden ones). Yep, It’s that easy :)

    Once you are done safely remove the USB device from the PC and attempt to boot from it. It should plain and simple work (provided your bios can boot from USB disk and you configured it to boot from USB disk properly). If your ISO image was working your USB stick should also be working.

    I hope this was helpful and if you have feedback it is always welcome.

    Share

  • How to create a Windows PE Disk (part 2)

    Posted on by Ionut Nica


    It is time for part 2 of this guide to making yourself a Windows PE disk. You can read more about the first part here. This post we will cover following: how to integrate drivers, add 3rd party/applications/files to your image, unmount the image and burn it to an ISO file. I also want to say that this is a scripted approach, and all data and scripts are in the E:\PE path in this guide. The scripted approach will come in handy when you are doing tens of rebuilds of the image because a certain driver will not integrate, or a registry file modification does not work.

    Integrate Drivers into WinPE Image

    First thing on the agenda here is to get the actual drivers you want to integrate into the Image. For most use cases it is enough to integrate Storage and Network drivers, and perhaps Chipset drivers. You also need to take into account the WinPE version you are building, in this guide, we build a x86 WinPE Image so my focus was on x86 drivers for Windows 7/ 2008 /2008 R2. Now go out and grab those CD’s or vendor provided tools (Hyper-V Integration components or VMware Tools).

    Some vendors ship other applications along with drivers, you don’t need the extra files most of the time, because WinPE doesn’t know how to use them most of the time. From the drivers in the list WinPE needs *.inf, *.cat and *.sys files corresponding to each driver you want to integrate and ANY other file specified in the *.inf file. Be patient with this process, as it can be sometimes painstaking and will cause you to rebuild your image until you get it right, until you find all the drivers and files you need :)

    Let’s take the example of VMware Tools for vSphere. If you want your WinPE to boot into vSphere and be able to see your storage adapters and network cards you need to integrate the drivers from VMware Tools.

    Step1: On a VM running Windows 2008/ Windows 7 on vSphere start an interactive VMwareTools Install.

    Step2: Install your VMware Tools and reboot VM. Now take a look in %programfiles%\vmware\vmware tools\drivers\ – driver heaven! Copy the needed folders from here into a folder called “E:\PE\Drivers\ESX_40″ (e:\PE is the location where we run our WinPE imaging process).

    For other drivers you may need to take a different approach. I will just share from my experience. Drivers can be in *.cab cabinet files, in *.zip files, inside MSI files, which you kinda have to install to get to (see vmware tools case), even install a driver and then look in device manager where the device driver exists and search for a similarly named *.inf, sys and *.cat file and all the other files referenced in the *.inf file.

    When you have all your drivers run this as administrator from a command prompt:

    c:
    cd \
    cd "%PROGRAMFILES%\windows aik\Tools\x86\Servicing"
    DISM /image:e:\pe\winpe_x86\mount /Add-Driver /driver:e:\PE\Drivers\ /recurse
    

    Here you run the DISM tool using /add-driver switch, /driver specifying where the drivers are located, and /recurse to make it look in all subfolders in e:\PE\Drivers. This is one of the sweetest things about the DISM, is that it can recursively search for drivers (in WinPE 2.0 you had to have 1 command per folder containing drivers).

    The output of the command should look like this:

    As you can see DISM searched the folder and found 84 drivers (inf files that he can integrate). I had 85 inf files inside that folder, one failed, and you see DISM threw and error. This is however just a “pre-flight” check, as there can still be errors during the actual integration:

    As you can see in this screenshot, DISM could not integrate some of the drivers and pointed to the DISM log file. This file can be found in %WINDIR%\Logs\DISM\dism.log.

    For those that just want to test their driver integration skip the next step.

    Adding Custom Scripts/Applications to the Image

    In an earlier post, I showed how to mount the WinPE Image. The Image was mounted under “E:\PE\winpe_x86\mount”. If you take a look in this folder you will notice a folder structure resembling a windows install…well that is exactly what it is – all Windows PE files unpacked, as they would look like if booted with the image. This means you can add files under %windir%\system32 of the image (in our case Windows\e:\pe\winpe_x86\mount\windows\System32) and you would be able to execute them as %windir%\system32 is in the %path% environment variable of the Windows OS. Note that not all apps run under Windows PE, sometimes it is a matter of trial and error.

    So it is just a matter of copying the files you need from a path, let’s say “e:\PE\CustomApps\” to wherever you want in the folder structure “e:\PE\winpe_x86\mount\”. Use a manual copy or do an xcopy like this for example:

    xcopy /y /r /F E:\PE\CustomApps E:\PE\winpe_x86\mount\Windows\System32
    

    It is a little known fact about Windows PE that it has a batch file called “startnet.cmd”. This file includes a command “wpeinit”. wpeinit is an executable that is run when WindowsPE boots on your system (more info here). While I don’t care much about wpeinit itself, I do care about startnet.cmd. This file you can modify/overwrite at this point with a custom made startnet.cmd that can start other scripts, check IP connectivity anything you need to do with your WinPE boot disk. Similar to putting custom apps on WinPE you can do this:

    xcopy /h /Y /R /F "E:\PE\CustomScripts\startnet.cmd" "E:\PE\winpe_x86\mount\Windows\System32\startnet.cmd"
    

    I am stressing the importance of this file because, you can access it only at boot time and it is “hard-coded” into the WIM file (you cannot change it after you unmount the WIM and build the ISO afterwards). Therefore, since startnet.cmd cannot be altered after building the image, it could make sense to have startnet.cmd point to a file say, autorun.cmd, that you can put on the root of the ISO file for example. And there are many ISO editing tools,so changes to autorun.cmd are easier to make, for editing a WIM things are not so straightforward.

    Still following this? Good, because the worst part is over :)

    Unmount Image and burn to ISO

    This last step is fairly easy. DISM has a parameter to unmount the image and commit the changes to the Image. If you remember in the beginning we copied boot.wim to winpe.wim. now we overwrite the existing boot.wim image with our serviced image. The commands below do just that:

    c:
    cd "%PROGRAMFILES%\windows aik\Tools\x86\Servicing"
    ::commit changes to image and unmount
    Dism.exe /Unmount-Wim /mountdir:E:\PE\winpe_x86\mount /commit
    copy E:\PE\winpe_x86\winpe.wim e:\pe\winpe_x86\ISO\sources\boot.wim /Y
    

    In the current state you have 2 options:

    1. Copy the contents of the E:\pe\winpe_x86\iso folder to a bootable USB stick or make an iso file out of it. For now let’s focus on making a ISO file.

    Microsoft delivered OSCDIMG with the WAIK, a utility that can create the bootable ISO for us.

    cd \
    C:
    cd "%PROGRAMFILES%\windows aik\Tools\x86"
    ::"-b" MUST BE next to path for etfsboot.com
    OSCDIMG -bE:\PE\winpe_x86\etfsboot.com -n -o E:\PE\winpe_x86\iso E:\PE\Current_ISO.iso
    

    Please note the comment in the script, “feature” or bug you don’t need a space between -b and the etfsboot,com file.

    This should have successfully built the image and you can burn it to a CD/mount it in a VM and enjoy a Microsoft Supported Windows 7 live CD :) . Before you go take a mental break from all this reading I just want to point out that Windows PE will crash if you run it on a system with insufficient memory.

    Why? The boot disk creates a Ramdisk where he loads Windows PE. If there is not enough RAM memory (typically you have this issue on old hardware or VM’s) it will crash and simply not load. As a rule of thumb the machine using it should have at least 1.8 -2.0 the size of the ISO file as RAM available on the machine.

    I hope this was helpful for others looking to use WinPE as boot disk and I appreciate any feedback you may have.

    Share

  • Removing specific message(s) from multiple Exchange 2007 mailboxes

    I seem to be doing quite a bit Powershell scripting these days, and some of related to MS Exchange 2007. One issue we had recently was that loose permissions on Distribution Lists with hundreds of users + too much spare time for some users generated a lot of unwanted message traffic. I don’t want to discuss prevention measures like restricting who can send emails to big DLs or using Microsoft AD RMS to restrict what can be done with emails. Our goal here is to clean up the mess ;) .

    Essentially you can get some info about the message and mailboxes, and use it with Export-mailbox to remove the data. That is how I initially found this link, but what is not written there is that you need to have all the prerequisites for running export-mailbox, and also running it on hundreds of mailboxes may take a while. I decided to do it my way,by building on what I found on that blog.

    This is “Mass Remove message(s) from mailboxes – My Way”.Depending on your situation you can apply these steps multiple times:

    1. Identify the message that started it all
    2. Track the message on the Exchange Servers and compile a list of unique recipients of the message.
    3. Remove the message from the offended mailboxes (there may be special requirements to perform the task, see here)

    Identify Message

    Getting the information should be pretty easy, someone probably forwarded you the copy of the message(s) to be dealt with. You want to get this info as a minimum: subject,sender,date and time message(s) was/were sent. When you have enough info, open the Exchange Management Console > Tools > Message Tracking and from there identify which of the events represent the time the message originally arrived on the Transport Servers. For that event grab the “MessageID” Value. We will use this in the following steps to find all events relating to that specific messageID.

    Track Message

    Assuming the worst case scenario you have to do tracking across all Exchange Transport Servers, the speed of the process depends on how close to your Exchange Transport Role Servers you are running the tracking. I suggest you make sure this process runs in the same LAN as the Exchange, especially the export-mailbox part. Anyhow, to get all messages sent by “baduser@foo.com” across all transport servers in your Exchange run this:

    $TrackingLogResults = get-transportserver | where {$_.Name -like "<optional filter>"} | foreach-object  {Get-MessageTrackingLog -EventId DELIVER -MessageID <MessageID from Step1> -ResultSize Unlimited -server $_}
    • Get-TransportServer gives you all the transport servers in the organization
    • Where clause filters the servers list, you can leave it out, it is helpful if your HUB transport servers are named in a specific way, and you know the message did not leave the organization, so you can exclude a search on the Edge Servers.
    • Foreach-Object cycles through all servers and performs the search
    • Get-MessageTrackingLog searches each transport server tracking log for DELIVER Events that correspond to messages with that specific MessageID. It returns unlimited results. The server that is being searched is piped from the Foreach cmdlet.
    • If you run the last cmdlet without the EventID filter, you will get lots of other EventID’s like fail,send,receive,routing,expand. You just need deliver, DELIVER is important because it basically says “OK, this message passed all of my checks I am now sending it to the Mailbox Server so it can submit it to the mailbox store”, so you get a list of just the actually affected mailboxes.

    This may take a while to run. Once it is finished we have to get the list of people that the message was sent to. The easy answer would be “why not just do $TrackingLogsResults | select-object Recipients and pipe it along to something else?”

    Well you can do that, but in some cases Recipients means actually a bunch of other addresses, and each recipient may appear multiple times in the entire list.

    e.g. – this could be a list returned by the “easy” command

    {john@foo.com}

    {John@foo.com,Jane@foo.com}

    {Jill@foo.com,Josh@foo.com,Jake@foo.com}

    Having duplicates is inefficient, everything will take longer in next steps. What I wanted was to have a list without duplicates, plus I get to show you some more “nice” scripting stuff ;)

    Compile Recipients List

    I spent quite some time figuring this out, so someone out there better find it useful :) . The next step involved a “google shovel” to “dig up” how to break up those objects into one big list. Then the plan was to have a list that just had the unique email addresses – ideally. So here’s the “magic”:

    $RecipientsExpanded = @()
    $RecipientsExpanded = $TrackingLogResults | foreach-object {$RecipientsExpanded  = $RecipientsExpanded  + ($_.Recipients)}
    $RecipientsGrouped = $RecipientsExpanded | group-object
    $UniqueRecipients = $RecipientsGrouped | select-object Name | sort-object -property name
    • We created a blank array object that will host all recipients addresses in “expanded form”.
    • For each result from the TrackingLog we added the array ($_.Recipients) to the $RecipientsExpanded array. At the end of this we have a single array with all the addresses, each an individual element in the array.
    • The Group-Object cmdlet is used to group all addresses by their name and in the end you have the list of unique email addresses.

    Actually remove offending messages

    Please see this link if you are planning to export the messages to PST. What is left to do is to take a page from the MSExchangeTeam blog and run get-mailbox| export-mailbox combo, only we are doing it on a reduced scale, only on the mailboxes that need it, that why I went through all the trouble of making that list!

    $MailboxesList = $UniqueRecipients | foreach-object {
          $Filter = "PrimarySmtpAddress -eq '"+$_.Name+"'"
          get-mailbox -ignoredefaultscope -resultsize unlimited -Filter $Filter}

    The code above handles this task for forests with child domains. I covered reasoning and use of -Ignoredefaultscope and -Filter in a previous post.

    #get current admin UserPrincipalName
    $Admin = [Security.Principal.WindowsIdentity]::GetCurrent().Name
    #elevating the administrator's account to fulll access over all affected mailboxes
    Add-MailboxPermission $MailboxesList -AccessRights FullAccess -User $Admin
    export-Mailbox -Identity $MailboxesList –ContentKeywords <enter part of message body> -Recipients <add recipients list> –TargetMailbox admin_ –TargetFolder "RecoveredEmails" –DeleteContent
    • The final step grants the admin user full access over the mailbox. The account being granted that right is $Admin, the account under which the script is running, it contains the UserPrincipalName of that account.
    • You also need to have admin rights on the “TargetMailbox” and the “TargetFolder” should also exist beforehand.
    • We export the offending message(s) using Export-Mailbox. Here it is important to be very careful and make the filtering as strict as possible, since here you cannot remove a message based on the MessageID, so you could end up removing many more messages. Refer to the documentation for export-mailbox, for all available switches for this purpose.

    After you run the last command get ready for some really long waiting, as it goes through all the mailboxes. Once it is finished, remove your permissions from those mailboxes.

    Remove-MailboxPermission $MailboxesList -AccessRights FullAccess -User $Admin

    Phew this was a long post, but validating everything I explained here, took a while. The post is also packed with bits and pieces that can be your building block for other Exchange Shell scripts. I tried to show you how to take Exchange TrackingLog data and build a list of unique recipient addresses that you can use to filter out an unwanted message you tracked in the logs, and do that using export-mailbox commandlet. If you have any feedback/corrections/omissions please feel free to leave a comment.

    Happy Scripting!

    Share


  • dinamic_sidebar 4 none

©2012 Ionut Nica Entries (RSS) and Comments (RSS)  Raindrops Theme  
View in: Mobile | Standard