Powershell: Get File Details and Owner Information in a GUI

Filter option example for out-gridview
Filter option example for out-gridview

A quick and dirty script to grab file details recursively including the owner info. A colleague was scouring the web looking for an app to do this. He also wanted to ability to quickly filter the results based on the last write time. This is a perfect use case for Out-GridView.

$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 seeing What’s happening now? in the Lync client. Sick of seeing the same old status update because you or your colleagues never pulled down that holiday message or #GoHawks update? Well, I was sick of it. I wanted a quick and dirty way to automate updating the Lync status message (aka ProfileNote).

After hours of scouring the internet and messing with multiple twitter libraries and outdated twitter code, I ran across this gem by Github: MyTwitter.psm1. While it was meant to post to twitter, a bit of hacking and this was born. Coupled with some other Lync Client work I’ve done, I had a workable solution.

This of course, wouldn’t be complete with a working example. All you need is to create the Oauth tokens on twitter, setup a scheduled task, and you’ll be auto updating Lync with everyone’s favorite infosec professional, Infosec Taylor Swift (@SwiftOnSecurity).

Steps:

  1. Go to https://apps.twitter.com/app/new and fill in the basic required information.
  2. Once created, click on the “Keys and Access Tokens” menu item
  3. At the bottom under “Token Actions”, select “Create my access token”
  4. Copy the tokens into the script.

Note:

  • 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 –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 requiring the Newtonsoft.Json libraries (NuGetGitHub). I also wanted to play around a bit with OneGet as I hadn’t had a need to yet.

Powershell 5 (5.0.9926.2 as of this writing) makes this easy. Here’s about all that’s needed to get this running and access the entire NuGet.org package repository.

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 geolocation data to keep you Lync client location up to date.

I regularly bounce around on different networks and vpn connections. I got tired of manually setting the location in Lync and found myself just ignoring it altogether. After doing some poking around, I decided to throw a powershell script together to just do the dirty work for me.

The script uses Telize for geoip data and DNSOMatic Telize for the external 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 trigger to fire off the script when you connect to a network: On an event; On event – Log: Microsoft-Windows-NetworkProfile/Operational, Source: Microsoft-Windows-NetworkProfile, Event ID: 10000

Note: I adjusted my personal version to detect when I’m on my company’s network so it won’t interfere with Lync setting to location for our office. Always test and understand the ramifications if using in a production environment.

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 Virtualbox.  It has some of the best tools for converting images between different hypervisors and is a leader in its support for different configurations.  Virtualbox is a great option for testing out new or different OS’s and configurations.  I don’t have to run a crippled hypervisor on my system or run some trialware just to try the latest bits.

Microsoft made a lot of changes to the Windows 8 network stack.  One of the more obvious is the speed in which network connections resume from sleep or standby.  Unfortunately, since running Virtualbox on the Dev Preview and on the Final Release, a bug in the Virtualbox Bridged Adapter breaks network connectivity.  Below are a few ways to work around this issue.  You can find more at the bugtraq I submitted to Oracle here: https://www.virtualbox.org/ticket/10317.

Option 1: Disable Virtualbox Bridged Adapter

For me, NAT adapters had too many draw backs to my testing and use.  But, this is how I have been running until I got off my laurels and automated the disable/enable routine outlined in option 3.

  1. In Windows, go to: Control PanelNetwork and InternetNetwork Connections
  2. Right click the affected/in-use network adapter and select properties
  3. In the items list, uncheck the VirtualBox Bridged Network Adapter
  4. Hit OK and you should be all set

Option 2: Disable/Re-Enable Adapters After Resuming

Doing this manually is extremely laborious and usually ends up with you giving up on bridged adapters.  Either select option 1 or 3.  But, it may save you if you just installed Virtualbox and haven’t had the chance to implement option 1 or 3.

  1. In Windows, go to: Control PanelNetwork and InternetNetwork Connections
  2. Right click the affected/in-use network adapter and select disable
  3. Right click the affected/in-use network adapter and select enable

Option 3: Automate Option 2

The following steps will create a task that will automatically disable and reenable you network adapters upon resume.  This will slow down reconnects but will allow the use of the VirtualBox Bridged Network Adapter.

  1. Create and save a script with the following command:
    1. gwmi Win32_NetworkAdapter -EnableAllPrivileges | ? { $_.PhysicalAdapter -and $_.NetEnabled } | % { $_.Disable(); $_.Enable() }
  2. Open the Event Log and go the the System Event Log
  3. Look/Search/Filter for Event ID 1, Source Power-Troubleshooter
  4. Right click on Event and select “Attach Task to this Event”
  5. In the Action section, under Program/script, enter: powershell.exe
  6. In the Arguments section, enter: c:scriptsvirtualbox-hack.ps1
    1. Make sure the path and name matches what you named your script in step 1
  7. On the finish screen, check the box “Open the Properties diaglog…” and press OK
  8. On the General tab of the task properties, select:
    1. “Run whether user is logged in or not”
    2. “Run with highest privilges”
  9. Select OK. It should prompt you for credentials. Enter the credentials and you are done.

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

Creating my custom service in Visual Studio

The IT ecosystem is rich with network monitoring systems (NMS). Each NMS has different capabilities, costs, and purposes in life. It is commonplace for me to come into a business that has invested in an NMS that doesn’t fit all their needs. You might ask, “What does this have to do with creating a Windows service?” Here is the scenario that brought this up.

A client has a monitoring solution for their Windows servers and some basic network up/down stats. Their internet connection had been flaky for a month or two. As we worked with their ISP, their connection continued to stay up but latency would spike and often drop packets. The monitoring never sees the link as down but the level of service is degraded and mostly unusable. The ISP can quickly reset the ports and fix the issue, but we want to know right when this happens to minimize downtime.

I setup a powershell script with the help of the team at ComputerPerformance.  This script is pretty straight forward. It uses the Test-Connection cmdlet and averages the latency 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 create a service that I can start and stop without actually impacting the underlying OS? I won’t go into the code too much but I have included the example C# source files in a zip at the bottom. I got this code from a MSDN social forum post.

To implement this, first make sure you read the forum post. You will need Visual Studio 2010 C# Express. Create a Windows Form Application and include references (Project –> Add References –> .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 included in a zip at the end of this article (I also included the executable from the code if you want to test it out). You really only need to edit the MyService.cs and MyServiceInstaller.cs to match the Service’s purpose in life (and write useful event log entries). Once that is done, build the program. You will get an error on build about a “Windows Service Start Failure” as shown below. (Any developers out there know how to make this build w/o throwing the obvious error?) Just ignore the error and grab the executable from the Debug folder of the project. Place the executable somewhere safe (I use %windir%SysWOW64 in my example below).

Next, we need to install the service. You can install the service using the SC command in Windows. In this example, I used the name “lattest” as the service name and defined the display name as well. You can find more information on the SC.exe command here. Note: those spaces after the equals (=) sign are required when using the SC command.  Here is the code I used to install this service on Server 2008 R2:

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

At this point, your script can be setup to run as a scheduled task to start or stop the service depending on the conditions you set. You can point your NMS to monitor the service and you can sleep peacefully at night knowing you are proactively monitoring the issue.

If this is temporary in nature and you want to remove the service, just remove it from your NMS, delete the service and executable and remove your scheduled task. The service can be removed by running:

sc delete lattest

Here is the code from the Visual Studio Project: MyService.zip

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

In a recent incident, an Exchange server had a complete volume failure during testing. Exchange 2010 was reinstalled but when installing Service Pack 1, it failed upgrading the Mailbox Role. Upon reviewing the log, I found the following line:

Database is mandatory on UserMailbox. Property Name: Database

The error doesn’t explain the problem very well but it is basically saying that there is a UserMailbox without a database, which should never happen. The failure of the volume and subsequent reinstall of 2010 left the arbitration mailboxes (and one or two user mailboxes) orphaned. Most of the suggestions to resolve this problem list doing things like deleting the system mailboxes and running “setup.com /PrepareAD”. After looking around, I was able to parse together a few other options and find a fix.

First, do a search in AD for the System mailboxes and make sure they exist in AD. (If they do not exist, check out this blog.)The three mailboxes are:

  • Discovery – SystemMailbox{e0dc1c29-89c3-4034-b678-e6c29d823ed9}
  • Message Approval – SystemMailbox{1f05a927-xxxx-xxxx-xxxx-xxxxxxxxxxxx} (where x is a random number)
  • Federated E-mail – FederatedEmail.4c1f4d8b-8179-4148-93bf-00a95fa1e042

Next, check out the status of their mailboxes:

Get-Mailbox –Arbitration

For this client, their Discovery and Message Approval mailboxes spat out error messages:

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 lovely, one-line powershell. It won’t do anything without prompting you first. To verify it fixes the issue after you run it, take the first half of the command (get-mailbox -arbitration) and run that again to confirm they are online and okay.

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

Hopefully this saves somebody from causing a bigger mess than necessary. After running this, I was able to install Service Pack 1 just fine. YMMV.

Edit: I found the DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852} was orphaned as well. To fix this mailbox, I just ran the following 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 migrations which went uncompleted. In both cases, I received the following error(s) when trying to view the properties of a recipient with spaces in its alias or when viewing the properties of the offline address book:

  • The properties on have invalid data. If you click OK, default values will be used instead and will be saved if you do not change them before hitting Apply or OK on the property page. If you click cancel, the object will be displayed read-only and corrupted values will be retained. The following values have invalid data: Alias.
  • WARNING: Object has been corrupted and it is in an inconsistent state. The following validation errors have been encountered: WARNING: is not valid for Alias.
  • Set- : is not valid for Alias.

Here is a screenshot of the error:

Exchange 2003 would allow an administrator to put spaces in the Alias attribute. That poses a problem for 2007 which is strict about the characters it allows in this attribute. In Exchange 2007 the following characters are considered valid: Strings formed with characters from a to z (uppercase or lowercase), digits from 0 to 9, !, #, $, %, &, ‘, *, +, -, /, =, ?, ^, _, `, {, |, } or ~. But, no spaces.

Going through you recipients one by one is a daunting task. Here is some code to automate this cleanup. Once you take care of this, you shouldn’t run into it again since the tools in Exchange 2007 won’t let you make the same mistake.

Clean up mailboxes:

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

Clean up public folders:

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 contact 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 distribution 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 baseline for generating reports within AD.  The two important pieces of information which were required to generate these reports were the ip address and FQDN of each domain controller.  The script would then connect to each individual system to gather data.  While I was at it, I added the MAC Address just to see what other pieces of data would be useful 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 recently asked to get a quick report of all Windows 7 computers within a multi-domain AD forest.  After banging my head into the keyboard for a while, I finally figured it out.  The script below should do the trick.

Also, if you use the OperatingSystemVersion attribute, you will find that Server 2008 R2 shares version “6.1 (7600)”.  So, the best way to find Windows 7 only, is to search for “Windows 7*” with the wildcard character against the OperatingSystem attribute.  That will ensure all Windows 7 versions are returned and will exclude Server 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
	}
}