Powershell: Get File Details and Owner Information in a GUI

Filter option example for out-gridview
Fil­ter option exam­ple for out-grid­view

A quick and dirty script to grab file details recur­sive­ly includ­ing the own­er info. A col­league was scour­ing the web look­ing for an app to do this. He also want­ed to abil­i­ty to quick­ly fil­ter the results based on the last write time. This is a per­fect use case for Out-Grid­View.

$path = "C:\Chocolatey"
$allfiles = @()

foreach ($item in (Get-ChildItem -Recurse -Path $path)) {
    $acl = Get-Acl -Path $item.FullName
    $allfiles += New-Object PSobject -Property @{
        LastWrite = $item.LastWriteTime
        Path = $item.FullName
        FileName = $item.Name
        Folder = $item.Directory
        Owner = $acl.Owner
    }
}

$allfiles | Out-GridView

 

Update Lync Notes With Twitter Status

Tired of see­ing What’s hap­pen­ing now? in the Lync client. Sick of see­ing the same old sta­tus update because you or your col­leagues nev­er pulled down that hol­i­day mes­sage or #GoHawks update? Well, I was sick of it. I want­ed a quick and dirty way to auto­mate updat­ing the Lync sta­tus mes­sage (aka Pro­fileNote).

After hours of scour­ing the inter­net and mess­ing with mul­ti­ple twit­ter libraries and out­dat­ed twit­ter code, I ran across this gem by Github: MyTwitter.psm1. While it was meant to post to twit­ter, a bit of hack­ing and this was born. Cou­pled with some oth­er Lync Client work I’ve done, I had a work­able solu­tion.

This of course, would­n’t be com­plete with a work­ing exam­ple. All you need is to cre­ate the Oauth tokens on twit­ter, set­up a sched­uled task, and you’ll be auto updat­ing Lync with every­one’s favorite infos­ec pro­fes­sion­al, Infos­ec Tay­lor Swift (@SwiftOnSecurity).

Steps:

  1. Go to https://apps.twitter.com/app/new and fill in the basic required infor­ma­tion.
  2. Once cre­at­ed, click on the “Keys and Access Tokens” menu item
  3. At the bot­tom under “Token Actions”, select “Cre­ate my access token”
  4. Copy the tokens into the script.

Note:

  • The secret tokens are sen­si­tive. Be like Tay­lor and pro­tect your secrets.
  • Your app per­mis­sions only need to be read-only. Be like Tay­lor and fol­low the prin­ci­ple of least priv­i­lege.
#requires –Version 3.0
<#
.SYNOPSIS 
Sets Lync 2013 Client's PersonalNote field with latest tweet from your favorite twitter personality:
@SwiftOnSecurity

.DESCRIPTION
Tired of What's happening today? Find out with the Set-LyncNoteWithTwitter.ps1 script. It sets the 
Lync 2013 Client's personal note to match the latest tweet from your favorite twitter personality. 
Authentication and authorization are handled throughTwitter's Oauth implementation. Everything else is 
via their REST API. The Lync COM is used to update the Lync client.

The secret tokens are sensitive. Be like Taylor and protect your secrets.
Your app permissions only need to be read-only. Be like Taylor and follow the principle of least privilege. 

****Requires Lync 2013 SDK.**** The SDK install requires Visual Studio 2010 SP1. To avoid installing 
Visual Studio, download the SDK, use 7-zip to extract the files from the install, and install the MSI 
relevant to your Lync Client build (x86/x64).

.INPUTS
None. You cannot pipe objects to Set-LyncNoteWithTwitter.ps1.

.OUTPUTS
None. Set-LyncNoteWithTwitter.ps1 does not generate any output.

.NOTES
Author Name:   Andrew Healey (@healeyio)
Creation Date: 2015-02-02
Version Date:  2015-02-02

.LINK
Author:          https://www.healey.io/blog/update-lync-notes-with-twitter-status/
Lync 2013 SDK:   http://www.microsoft.com/en-us/download/details.aspx?id=36824
Some code referenced from:
   MyTwitter:    https://github.com/MyTwitter/MyTwitter

.EXAMPLE
PS C:\PS> .\Set-LyncNoteWithTwitter.ps1

#>


## Parameters
[string]$Consumer_Key =        'abcdefghijklmnopqrstuvwxyz'
[string]$Consumer_Secret =     'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'
[string]$Access_Token =        'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'
[string]$Access_Token_Secret = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst'
[string]$screen_name =         'SwiftOnSecurity'
[int]   $count =                1
[string]$exclude_replies =     'true'
[string]$include_rts =         'false'
[string]$HttpEndPoint =        'https://api.twitter.com/1.1/statuses/user_timeline.json'

[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null

## Generate a random 32-byte string. Strip out '=' per twitter req's
$OauthNonce = [System.Convert]::ToBase64String(([System.Text.Encoding]::ASCII.GetBytes("$([System.DateTime]::Now.Ticks.ToString())12345"))).Replace('=', 'g')
Write-Verbose "Generated Oauth none string '$OauthNonce'"
			
## Find the total seconds since 1/1/1970 (epoch time)
$EpochTimeNow = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null)
Write-Verbose "Generated epoch time '$EpochTimeNow'"
$OauthTimestamp = [System.Convert]::ToInt64($EpochTimeNow.TotalSeconds).ToString();
Write-Verbose "Generated Oauth timestamp '$OauthTimestamp'"
			
## Build the signature
$SignatureBase = "$([System.Uri]::EscapeDataString($HttpEndPoint))&"
$SignatureParams = @{
	'oauth_consumer_key' =     $Consumer_Key;
	'oauth_nonce' =            $OauthNonce;
	'oauth_signature_method' = 'HMAC-SHA1';
	'oauth_timestamp' =        $OauthTimestamp;
	'oauth_token' =            $Access_Token;
	'oauth_version' =          '1.0';
}

## Add Signature Params
$SignatureParams.screen_name =     $screen_name
$SignatureParams.exclude_replies = $exclude_replies
$SignatureParams.include_rts =     $include_rts
$SignatureParams.count =           $count
			
## Create a string called $SignatureBase that joins all URL encoded 'Key=Value' elements with a &
## Remove the URL encoded & at the end and prepend the necessary 'POST&' verb to the front
$SignatureParams.GetEnumerator() | sort name | foreach { 
    Write-Verbose "Adding '$([System.Uri]::EscapeDataString(`"$($_.Key)=$($_.Value)&`"))' to signature string"
    $SignatureBase += [System.Uri]::EscapeDataString("$($_.Key)=$($_.Value)&".Replace(',','%2C').Replace('!','%21'))
}
$SignatureBase = $SignatureBase.TrimEnd('%26')
$SignatureBase = 'GET&' + $SignatureBase
Write-Verbose "Base signature generated '$SignatureBase'"
			
## Create the hashed string from the base signature
$SignatureKey = [System.Uri]::EscapeDataString($Consumer_Secret) + "&" + [System.Uri]::EscapeDataString($Access_Token_Secret);
			
$hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
$hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($SignatureKey);
$OauthSignature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($SignatureBase)));
Write-Verbose "Using signature '$OauthSignature'"
			
## Build the authorization headers using most of the signature headers elements.  This is joining all of the 'Key=Value' elements again
## and only URL encoding the Values this time while including non-URL encoded double quotes around each value
$AuthorizationParams = $SignatureParams
$AuthorizationParams.Add('oauth_signature', $OauthSignature)
			
## Remove any REST API call-specific params from the authorization params
$AuthorizationParams.Remove('exclude_replies')
$AuthorizationParams.Remove('include_rts')
$AuthorizationParams.Remove('screen_name')
$AuthorizationParams.Remove('count')
			
$AuthorizationString = 'OAuth '
$AuthorizationParams.GetEnumerator() | sort name | foreach { $AuthorizationString += $_.Key + '="' + [System.Uri]::EscapeDataString($_.Value) + '",' }
$AuthorizationString = $AuthorizationString.TrimEnd(',')
Write-Verbose "Using authorization string '$AuthorizationString'"

## Build URI Body
$URIBody = "?count=$count&exclude_replies=$exclude_replies&include_rts=$include_rts&screen_name=$screen_name"
Write-Verbose "Using GET URI: $($HttpEndPoint + $Body)"
$tweet = Invoke-RestMethod -URI $($HttpEndPoint + $URIBody) -Method Get -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"

## Verify lync 2013 object model dll is either in script directory or SDK is installed
$lyncSDKPath = "Microsoft Office\Office15\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.dll"
$lyncSDKError = "Lync 2013 SDK is required. Download here and install: http://www.microsoft.com/en-us/download/details.aspx?id=36824"

if (-not (Get-Module -Name Microsoft.Lync.Model)) {
    if (Test-Path (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath
    }
    elseif (Test-Path (Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath
    }
    else {
        $fileError = New-Object System.io.FileNotFoundException("SDK Not Found: $lyncSDKError")
        throw $fileError
    } # End SDK/DLL check
    try {
        Import-Module -Name $lyncPath -ErrorAction Stop
    }
    catch {
        $fileError = New-Object System.io.FileNotFoundException ("Import-Module Error: $lyncSDKError")
        throw $fileError
    } # End object model import
} # End dll check

## Check if Lync is signed in, otherwise, nothing to do
$Client = [Microsoft.Lync.Model.LyncClient]::GetClient()
if ($Client.State -eq "SignedIn") {
    ## Set PersonalNote in Lync
    $LyncInfo = New-Object 'System.Collections.Generic.Dictionary[Microsoft.Lync.Model.PublishableContactInformationType, object]'
    $LyncInfo.Add([Microsoft.Lync.Model.PublishableContactInformationType]::PersonalNote, "@$($screen_name): $($tweet.text)")
    $Self = $Client.Self
    $Publish = $Self.BeginPublishContactInformation($LyncInfo, $null, $null)
    $Self.EndPublishContactInformation($Publish)
}
else {
    Write-Warning "Lync must be signed in."
} # End client sign-in check

 

Using NuGet with Powershell 5.0

I had a project requir­ing the Newtonsoft.Json libraries (NuGet - GitHub). I also want­ed to play around a bit with OneGet as I had­n’t had a need to yet.

Pow­er­shell 5 (5.0.9926.2 as of this writ­ing) makes this easy. Here’s about all that’s need­ed to get this run­ning and access the entire NuGet.org pack­age repos­i­to­ry.

Register-PackageSource -Name NuGet -ProviderName Chocolatey -Location https://nuget.org/api/v2/
Get-PackageProvider -Name NuGet -ForceBootstrap
Find-Package -ProviderName NuGet -Name Newtonsoft.Json | Install-Package

 

Update Lync Client Location with IP GeoLocation

Use IP geolocation data to keep you Lync client location up to date.
Use IP geolo­ca­tion data to keep you Lync client loca­tion up to date.

I reg­u­lar­ly bounce around on dif­fer­ent net­works and vpn con­nec­tions. I got tired of man­u­al­ly set­ting the loca­tion in Lync and found myself just ignor­ing it alto­geth­er. After doing some pok­ing around, I decid­ed to throw a pow­er­shell script togeth­er to just do the dirty work for me.

The script uses Telize for geoip data and DNSO­Mat­ic Telize for the exter­nal IP. The script requires the Microsoft.Lync.Model.dll from the Lync 2013 SDK (15.0.4603.1000 as of this post). You can find the Lync Client 2013 SDK here.

You can then add an event trig­ger to fire off the script when you con­nect to a net­work: On an event; On event - Log: Microsoft-Win­dows-Net­workPro­file/­Op­er­a­tional, Source: Microsoft-Win­dows-Net­workPro­file, Event ID: 10000

Note: I adjust­ed my per­son­al ver­sion to detect when I’m on my com­pa­ny’s net­work so it won’t inter­fere with Lync set­ting to loca­tion for our office. Always test and under­stand the ram­i­fi­ca­tions if using in a pro­duc­tion envi­ron­ment.

Gist on Github: Update-LyncLocation.ps1

#requires –Version 3.0
<#
.SYNOPSIS 
Updates Lync 2013 Client's location information with geolocation data based on internet ip address.

.DESCRIPTION
The Update-LyncLocation.ps1 script updates the Lync 2013 Client's location information. It uses the 
Telize web service to determine your external ip address and then queries Telize to collect publicly 
available geolocation information to determine your location. That data is then parsed into usable 
information and published to the Lync client.

****Requires Lync 2013 SDK.**** The SDK install requires Visual Studio 2010 SP1. To avoid installing 
Visual Studio, download the SDK, use 7-zip to extract the files from the install, and install the MSI 
relevant to your Lync Client build (x86/x64).

.INPUTS
None. You cannot pipe objects to Update-LyncLocation.ps1.

.OUTPUTS
None. Update-LyncLocation.ps1 does not generate any output.

.NOTES
Author Name:   Andrew Healey (@healeyio)
Creation Date: 2015-01-04
Version Date:  2015-01-26

.LINK
Author: https://www.healey.io/blog/update-lync-client-location-with-geolocation
Lync 2013 SDK: http://www.microsoft.com/en-us/download/details.aspx?id=36824
IP Geolocation Web Service: http://www.telize.com/

.EXAMPLE
PS C:\PS> .\Update-LyncLocation.ps1

#>


# Verify lync 2013 object model dll is either in script directory or SDK is installed
$lyncSDKPath = "Microsoft Office\Office15\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.dll"
$lyncSDKError = "Lync 2013 SDK is required. Download here and install: http://www.microsoft.com/en-us/download/details.aspx?id=36824"

if (-not (Get-Module -Name Microsoft.Lync.Model)) {
    if (Test-Path (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath
    }
    elseif (Test-Path (Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath
    }
    else {
        $fileError = New-Object System.io.FileNotFoundException("SDK Not Found: $lyncSDKError")
        throw $fileError
    } # End SDK/DLL check
    try {
        Import-Module -Name $lyncPath -ErrorAction Stop
    }
    catch {
        $fileError = New-Object System.io.FileNotFoundException ("Import-Module Error: $lyncSDKError")
        throw $fileError
    } # End object model import
} # End dll check

# Check if Lync is signed in, otherwise, nothing to do
$Client = [Microsoft.Lync.Model.LyncClient]::GetClient()
if ($Client.State -eq "SignedIn") {
    # Get external ip address
    $WanIP = (Invoke-WebRequest -Uri "http://ip4.telize.com/" -UseBasicParsing).Content
    # Get geolocation data
    $data = Invoke-WebRequest -Uri "http://www.telize.com/geoip/$WanIP" -UseBasicParsing | ConvertFrom-Json
    $data
    ### Format the location from returned geolocation ###
    ###    More Info Here: http://www.telize.com/     ###
    # Deal with oddities like anonymous proxies
    if (($data.continent_code -eq "--") -or ($data.continent_code -eq $null)) {$location = "$($data.isp)"}
    # If the city and state are not null, make it City, State
    elseif (($data.region_code -ne $null) -and ($data.city -ne $null)) {$location = "$($data.city), $($data.region_code)"}
    # If the city is null but state/region has a value, make it Region, Country
    elseif (($data.region -ne $null) -and ($data.city -eq $null)) {$location = "$($data.region), $($data.country_code3)"}
    # Else, just output the Country
    else {$location = "$($data.country)"}

    # Update location in Lync
    $LyncInfo = New-Object 'System.Collections.Generic.Dictionary[Microsoft.Lync.Model.PublishableContactInformationType, object]'
    $LyncInfo.Add([Microsoft.Lync.Model.PublishableContactInformationType]::LocationName, $location)
    $Self = $Client.Self
    $Publish = $Self.BeginPublishContactInformation($LyncInfo, $null, $null)
    $Self.EndPublishContactInformation($Publish)
}
else {
    Write-Warning "Lync must be signed in."
} # End client sign-in check

 

Virtualbox on Windows 8 Host - Poweshell Code to Fix Resume from Standby Network Issue

I’ve always been a big fan of Vir­tu­al­box.  It has some of the best tools for con­vert­ing images between dif­fer­ent hyper­vi­sors and is a leader in its sup­port for dif­fer­ent con­fig­u­ra­tions.  Vir­tu­al­box is a great option for test­ing out new or dif­fer­ent OS’s and con­fig­u­ra­tions.  I don’t have to run a crip­pled hyper­vi­sor on my sys­tem or run some tri­al­ware just to try the lat­est bits.

Microsoft made a lot of changes to the Win­dows 8 net­work stack.  One of the more obvi­ous is the speed in which net­work con­nec­tions resume from sleep or stand­by.  Unfor­tu­nate­ly, since run­ning Vir­tu­al­box on the Dev Pre­view and on the Final Release, a bug in the Vir­tu­al­box Bridged Adapter breaks net­work con­nec­tiv­i­ty.  Below are a few ways to work around this issue.  You can find more at the bug­traq I sub­mit­ted to Ora­cle here: https://www.virtualbox.org/ticket/10317.

Option 1: Disable Virtualbox Bridged Adapter

For me, NAT adapters had too many draw backs to my test­ing and use.  But, this is how I have been run­ning until I got off my lau­rels and auto­mat­ed the disable/enable rou­tine out­lined in option 3.

  1. In Win­dows, go to: Con­trol Pan­el­Net­work and Inter­net­Net­work Con­nec­tions
  2. Right click the affect­ed/in-use net­work adapter and select prop­er­ties
  3. In the items list, uncheck the Vir­tu­al­Box Bridged Net­work Adapter
  4. Hit OK and you should be all set

Option 2: Disable/Re-Enable Adapters After Resuming

Doing this man­u­al­ly is extreme­ly labo­ri­ous and usu­al­ly ends up with you giv­ing up on bridged adapters.  Either select option 1 or 3.  But, it may save you if you just installed Vir­tu­al­box and haven’t had the chance to imple­ment option 1 or 3.

  1. In Win­dows, go to: Con­trol Pan­el­Net­work and Inter­net­Net­work Con­nec­tions
  2. Right click the affect­ed/in-use net­work adapter and select dis­able
  3. Right click the affect­ed/in-use net­work adapter and select enable

Option 3: Automate Option 2

The fol­low­ing steps will cre­ate a task that will auto­mat­i­cal­ly dis­able and reen­able you net­work adapters upon resume.  This will slow down recon­nects but will allow the use of the Vir­tu­al­Box Bridged Net­work Adapter.

  1. Cre­ate and save a script with the fol­low­ing com­mand:
    1. gwmi Win32_NetworkAdapter -EnableAll­Priv­i­leges | ? { $_.PhysicalAdapter -and $_.NetEnabled } | % { $_.Disable(); $_.Enable() }
  2. Open the Event Log and go the the Sys­tem Event Log
  3. Look/Search/Filter for Event ID 1, Source Pow­er-Trou­bleshoot­er
  4. Right click on Event and select “Attach Task to this Event”
  5. In the Action sec­tion, under Program/script, enter: powershell.exe
  6. In the Argu­ments sec­tion, enter: c:scriptsvirtualbox-hack.ps1
    1. Make sure the path and name match­es what you named your script in step 1
  7. On the fin­ish screen, check the box “Open the Prop­er­ties dia­glog…” and press OK
  8. On the Gen­er­al tab of the task prop­er­ties, select:
    1. Run whether user is logged in or not”
    2. Run with high­est priv­ilges”
  9. Select OK. It should prompt you for cre­den­tials. Enter the cre­den­tials and you are done.

C# Express - Create a Dummy or Placeholder Windows Service for Monitoring

Cre­at­ing my cus­tom ser­vice in Visu­al Stu­dio

The IT ecosys­tem is rich with net­work mon­i­tor­ing sys­tems (NMS). Each NMS has dif­fer­ent capa­bil­i­ties, costs, and pur­pos­es in life. It is com­mon­place for me to come into a busi­ness that has invest­ed in an NMS that does­n’t fit all their needs. You might ask, “What does this have to do with cre­at­ing a Win­dows ser­vice?” Here is the sce­nario that brought this up.

A client has a mon­i­tor­ing solu­tion for their Win­dows servers and some basic net­work up/down stats. Their inter­net con­nec­tion had been flaky for a month or two. As we worked with their ISP, their con­nec­tion con­tin­ued to stay up but laten­cy would spike and often drop pack­ets. The mon­i­tor­ing nev­er sees the link as down but the lev­el of ser­vice is degrad­ed and most­ly unus­able. The ISP can quick­ly reset the ports and fix the issue, but we want to know right when this hap­pens to min­i­mize down­time.

I set­up a pow­er­shell script with the help of the team at Com­put­er­Per­for­mance.  This script is pret­ty straight for­ward. It uses the Test-Con­nec­tion cmdlet and aver­ages the laten­cy to a remote host.

# Gets the average latency between you and $Server
# Using IP Addresses is recommended
$Server = "8.8.8.8"
$PingServer = Test-Connection -count 20 $Server
$Avg = ($PingServer | Measure-Object ResponseTime -average)
$Calc = [System.Math]::Round($Avg.average)
If ($Calc -gt 100 -Or $Calc -eq 0) {stop-service MyService} Else {start-service MyService}

Now, for the tricky part. How do I cre­ate a ser­vice that I can start and stop with­out actu­al­ly impact­ing the under­ly­ing OS? I won’t go into the code too much but I have includ­ed the exam­ple C# source files in a zip at the bot­tom. I got this code from a MSDN social forum post.

To imple­ment this, first make sure you read the forum post. You will need Visu­al Stu­dio 2010 C# Express. Cre­ate a Win­dows Form Appli­ca­tion and include ref­er­ences (Project --> Add Ref­er­ences --> .NET) to System.Configuration.Install and System.ServiceProcess. You will need three files in your project: MyService.cs, MyServiceInstaller.cs and Program.cs. These are includ­ed in a zip at the end of this arti­cle (I also includ­ed the exe­cutable from the code if you want to test it out). You real­ly only need to edit the MyService.cs and MyServiceInstaller.cs to match the Ser­vice’s pur­pose in life (and write use­ful event log entries). Once that is done, build the pro­gram. You will get an error on build about a “Win­dows Ser­vice Start Fail­ure” as shown below. (Any devel­op­ers out there know how to make this build w/o throw­ing the obvi­ous error?) Just ignore the error and grab the exe­cutable from the Debug fold­er of the project. Place the exe­cutable some­where safe (I use %windir%SysWOW64 in my exam­ple below).

Next, we need to install the ser­vice. You can install the ser­vice using the SC com­mand in Win­dows. In this exam­ple, I used the name “lat­test” as the ser­vice name and defined the dis­play name as well. You can find more infor­ma­tion on the SC.exe com­mand here. Note: those spaces after the equals (=) sign are required when using the SC com­mand.  Here is the code I used to install this ser­vice on Serv­er 2008 R2:

sc create lattest binpath= "C:WindowsSysWOW64MyService.exe" displayname= "Latency Test" start= auto

At this point, your script can be set­up to run as a sched­uled task to start or stop the ser­vice depend­ing on the con­di­tions you set. You can point your NMS to mon­i­tor the ser­vice and you can sleep peace­ful­ly at night know­ing you are proac­tive­ly mon­i­tor­ing the issue.

If this is tem­po­rary in nature and you want to remove the ser­vice, just remove it from your NMS, delete the ser­vice and exe­cutable and remove your sched­uled task. The ser­vice can be removed by run­ning:

sc delete lattest

Here is the code from the Visu­al Stu­dio Project: MyService.zip

Installing Exchange 2010 Service Pack 1 Fails At Mailbox Role: Database is mandatory on UserMailbox.

In a recent inci­dent, an Exchange serv­er had a com­plete vol­ume fail­ure dur­ing test­ing. Exchange 2010 was rein­stalled but when installing Ser­vice Pack 1, it failed upgrad­ing the Mail­box Role. Upon review­ing the log, I found the fol­low­ing line:

Database is mandatory on UserMailbox. Property Name: Database

The error does­n’t explain the prob­lem very well but it is basi­cal­ly say­ing that there is a User­Mail­box with­out a data­base, which should nev­er hap­pen. The fail­ure of the vol­ume and sub­se­quent rein­stall of 2010 left the arbi­tra­tion mail­box­es (and one or two user mail­box­es) orphaned. Most of the sug­ges­tions to resolve this prob­lem list doing things like delet­ing the sys­tem mail­box­es and run­ning “setup.com /PrepareAD”. After look­ing around, I was able to parse togeth­er a few oth­er options and find a fix.

First, do a search in AD for the Sys­tem mail­box­es and make sure they exist in AD. (If they do not exist, check out this blog.)The three mail­box­es are:

  • Dis­cov­ery - SystemMailbox{e0dc1c29-89c3-4034-b678-e6c29d823ed9}
  • Mes­sage Approval - SystemMailbox{1f05a927-xxxx-xxxx-xxxx-xxxxxxxxxxxx} (where x is a ran­dom num­ber)
  • Fed­er­at­ed E-mail - FederatedEmail.4c1f4d8b-8179-4148-93bf-00a95fa1e042

Next, check out the sta­tus of their mail­box­es:

Get-Mailbox –Arbitration

For this client, their Dis­cov­ery and Mes­sage Approval mail­box­es spat out error mes­sages:

WARNING: The object XXXXXXXX.XXXXX/Users/SystemMailbox{e0dc1c29-89c3-4034-b678-e6c29d823ed9} 
has been corrupted, and it's in an inconsistent state. The following validation errors happened:
WARNING: Database is mandatory on UserMailbox.

The fix is a love­ly, one-line pow­er­shell. It won’t do any­thing with­out prompt­ing you first. To ver­i­fy it fix­es the issue after you run it, take the first half of the com­mand (get-mail­box -arbi­tra­tion) and run that again to con­firm they are online and okay.

Get-Mailbox -Arbitration | Set-Mailbox -Arbitration –Database "Mailbox Database XXX"

Hope­ful­ly this saves some­body from caus­ing a big­ger mess than nec­es­sary. After run­ning this, I was able to install Ser­vice Pack 1 just fine. YMMV.

Edit: I found the DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852} was orphaned as well. To fix this mail­box, I just ran the fol­low­ing PoSH.

Get-Mailbox -Identity "DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}" | Set-Mailbox –Database "Mailbox Database XXX"

Exchange 2007+: Aliases have invalid data

Twice in the past two weeks, I have come across Exchange 2003 to Exchange 2007 migra­tions which went uncom­plet­ed. In both cas­es, I received the fol­low­ing error(s) when try­ing to view the prop­er­ties of a recip­i­ent with spaces in its alias or when view­ing the prop­er­ties of the offline address book:

  • The prop­er­ties on have invalid data. If you click OK, default val­ues will be used instead and will be saved if you do not change them before hit­ting Apply or OK on the prop­er­ty page. If you click can­cel, the object will be dis­played read-only and cor­rupt­ed val­ues will be retained. The fol­low­ing val­ues have invalid data: Alias.
  • WARNING: Object has been cor­rupt­ed and it is in an incon­sis­tent state. The fol­low­ing val­i­da­tion errors have been encoun­tered: WARNING: is not valid for Alias.
  • Set- : is not valid for Alias.

Here is a screen­shot of the error:

Exchange 2003 would allow an admin­is­tra­tor to put spaces in the Alias attribute. That pos­es a prob­lem for 2007 which is strict about the char­ac­ters it allows in this attribute. In Exchange 2007 the fol­low­ing char­ac­ters are con­sid­ered valid: Strings formed with char­ac­ters from a to z (upper­case or low­er­case), dig­its from 0 to 9, !, #, $, %, &, ‘, *, +, -, /, =, ?, ^, _, ‘, {, |, } or ~. But, no spaces.

Going through you recip­i­ents one by one is a daunt­ing task. Here is some code to auto­mate this cleanup. Once you take care of this, you should­n’t run into it again since the tools in Exchange 2007 won’t let you make the same mis­take.

Clean up mail­box­es:

Get-Mailbox | Where {$_.Alias -like "* *"} | ForEach-Object {Set-Mailbox $_.Name -Alias:($_.Alias -Replace " ","")}

Clean up pub­lic fold­ers:

Get-PublicFolder | Where {$_.Alias -like "* *"} | ForEach-Object {Set-PublicFolder $_.Name -Alias:($_.Alias -Replace " ","")}
Get-PublicFolder -Identity "" -Recurse -ResultSize Unlimited | Foreach { Set-PublicFolder -Identity $_.Identity -Name $_.Name.Trim()}

Clean up con­tact objects:

Get-MailContact -ResultSize unlimited | foreach {$_.alias = $_.alias -replace 's|,|.'; $_} | Set-MailContact
Get-Contact | Where {$_.Alias -like "* *"} | ForEach-Object {Set-Contact $_.Name -Alias:($_.Alias -Replace " ","")}

Clean up dis­tri­b­u­tion groups:

Get-DistributionGroup | Where {$_.Alias -like "* *"} | ForEach-Object {Set-DistributionGroup $_.Name -Alias:($_.Alias -Replace " ","")}

Check for any objects that still throw errors:

Get-PublicFolder | findstr "Warning"
Get-Contact -resultsize unlimited | findstr "Warning"
Get-Mailbox -resultsize unlimited | findstr "Warning"
Get-DistributionGroup -resultsize unlimited | findstr "Warning"

Rebuild your address lists:

Set-AddressList "All Users" -IncludedRecipients MailboxUsers
Set-AddressList "All Groups" -IncludedRecipients Mailgroups
Set-AddressList "All Contacts" -IncludedRecipients MailContacts
Set-AddressList "Public Folders" -RecipientFilter {RecipientType -eq "PublicFolder"}
Set-GlobalAddressList "Default Global Address List" -RecipientFilter {(Alias -ne $null -and (ObjectClass -eq 'user' -or ObjectClass -eq 'contact' -or ObjectClass -eq 'msExchSystemMailbox' -or ObjectClass -eq 'msExchDynamicDistributionList' -or ObjectClass -eq 'group' -or ObjectClass -eq 'publicFolder'))}

Powershell: Getting the IP Address, FQDN and MAC Address of Each Domain Controller

I was asked to get a base­line for gen­er­at­ing reports with­in AD.  The two impor­tant pieces of infor­ma­tion which were required to gen­er­ate these reports were the ip address and FQDN of each domain con­troller.  The script would then con­nect to each indi­vid­ual sys­tem to gath­er data.  While I was at it, I added the MAC Address just to see what oth­er pieces of data would be use­ful out of the Win32_NetworkAdapterConfiguration class.

#Enter the fqdn of your forest/domain
$fqdn = "fully.qualified.domain.name"
#Create Empty HashTable
$ht = New-Object psobject | Select FQDN, MACAddress, IPAddress

#Enumerate Domain Controllers
$context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$fqdn)
$dclist = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($context)
ForEach ($strComputer in $dclist) {
	#Get IP Info of each DC
	$colItems = GWMI -cl "Win32_NetworkAdapterConfiguration" -name "rootCimV2" -comp `
                $strComputer.name -filter "IpEnabled = TRUE"
	ForEach ($objItem in $colItems){
        $ht.FQDN = $strComputer
        $ht.MACAddress = $objItem.MacAddress
        $ht.IPAddress = $objItem.IpAddress
	}
    $ht
}

Powershell: Using PoSH to Search Across Multiple Domains in Forest


I was recent­ly asked to get a quick report of all Win­dows 7 com­put­ers with­in a mul­ti-domain AD for­est.  After bang­ing my head into the key­board for a while, I final­ly fig­ured it out.  The script below should do the trick.

Also, if you use the Oper­at­ingSys­temVer­sion attribute, you will find that Serv­er 2008 R2 shares ver­sion “6.1 (7600)”.  So, the best way to find Win­dows 7 only, is to search for “Win­dows 7*” with the wild­card char­ac­ter against the Oper­at­ingSys­tem attribute.  That will ensure all Win­dows 7 ver­sions are returned and will exclude Serv­er 2008 R2 from your results.

#Get Domain List
$objForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = @($objForest.Domains | Select-Object Name)
$Domains = $DomainList | foreach {$_.Name}


#Act on each domain
foreach($Domain in ($Domains))
{
	Write-Host "Checking $Domain" -fore red
	$ADsPath = [ADSI]"LDAP://$Domain"
	$objSearcher = New-Object System.DirectoryServices.DirectorySearcher($ADsPath)
	$objSearcher.Filter = "(&(objectCategory=Computer)(operatingSystem=Windows 7*))"
	$objSearcher.SearchScope = "Subtree"

	$colResults = $objSearcher.FindAll()
	
	foreach ($objResult in $colResults)
	{
		$Computer = $objResult.GetDirectoryEntry()
		$Computer.DistinguishedName
	}
}