Active Directory Migrations: Setting user attributes

In the ongoing series of AD Migrations….

These next couple are very specific to our environment but I’m putting them out here for posterity. Both use the Quest AD Powershell tools, which are very powerful tools when it comes to object manipulation in AD. I suggest you go download and install them immediately – Quest

This first one sets an extended attribute on the user in the source domain so that we know they’ve been migrated. It’s more for a key for reports and stuff, but can be good info to have.

The second combines a couple of things. The first thing it does is set the displayname and UPN for the migrated user to go with our new standards (oh, did I mention we’re changing the UPN but leaving the SAM alone and changing the displayname?)

It also turns on ActiveSync for the users if they need it. Made the most sense for our scripts to put it here.

AttribMig.ps1:


##filename attribmig.ps1
## Set transcript output
$Tranoutput="d:\migration\Outputs\" + $date + "SourceSIP.txt"
start-transcript -path $Tranoutput -append

##call include file
. .\params.ps1

## import our import file
$import=import-csv $importfile

##Set migrated attribute in source
## We're just setting an extended attribute using QAD and piping that out to the screen.

ForEach ($item in $import){
 write-host "Attribute being set for " $item.sourcename -foregroundcolor yellow
 set-qaduser -service $SourceDC -identity $item.sourcename -objectAttributes @{"extensionattribute4"="MigratedToCorp"}
}
stop-transcript

DisplayName.ps1


## Set our transcript and output file
$Tranoutput="d:\migration\Outputs\" + $date + "UPN.txt"
start-transcript -path $Tranoutput -append

##call include file
. .\params.ps1

##Create Sessions to Exchange 2010 in Target
$ExchSession=New-PSSession -ConfigurationName Microsoft.Exchange -connectionuri $ExchURI -credential $LocalCredentials

## Import our file and session

$import=import-csv $importfile
import-pssession $ExchSession|out-null

##Perform set user to fix displayname and upn
## Set the displayname and UPN based off of input file

foreach ($item in $Import){
 $UPN=$item.newupn+"@csgicorp.com"
 $AS=$item.ActiveSync.ToUpper()
 write-host "Setting UPN and AS for " $item.smtp -foregroundcolor yellow
 set-user -identity $item.smtp -displayname $item.displayname -userprincipalname $UPN

## Check if user is supposed to have ActiveSync Enabled and turn it on if they do

if ($AS -eq "YES"){set-casmailbox -identity $item.smtp -activesyncenabled $true}

}

## Clean up after ourselves and stop transcript
remove-pssession $ExchSession
stop-transcript

Advertisements

Active Directory Migrations: User ADMT and Mailbox Move

I’m not going to go thru the screenshots of how to ADMT a user over. That’s documented well enough elsewhere (i.e. HERE).

All I’ll really say about that is that you NEVER want to migrate Exchange attributes of a user. Never, ever, ever. The process for migrating a user and a mailbox is simple enough:

  • Run prepare-moverequest.ps1 for each mailbox that needs to be created. It creates an MEU (mail enabled user) on the target side with all the exchange attributes you need. Essentially a fancy contact
  • ADMT the user (see above). Exclude: homeMDB, homeMTA, mailnickname, all the msExch*, all the msRTCSIP, proxyaddresses, targetaddress
  • Perform a move mailbox (see below). The move mailbox converts the source account into a MEU on the source domain. This is needed for mailflow.

## Get date, set our transcript file, etc.

$date=get-date -format "yyyyMMdd"
$Tranoutput="d:\migration\Outputs\" + $date + "Mailboxmove.txt"
start-transcript -path $Tranoutput -append

##call include file
## This ensures that the variables are loaded, altho if you followed the previous article they already should be
. .\params.ps1

## Create Remote Powershell session to the Exchange 2010 server
## Greate for making sure all your command can be run from one place
$ExchSession=New-PSSession -ConfigurationName Microsoft.Exchange -connectionuri $ExchURI -credential $LocalCredentials
import-pssession $ExchSession|out-null
## Import the include file
$import=import-csv $ImportFile
## Do a move request for each mailbox using all of our variables
## Targetdeliverydomain is important to ensure mailflow. It should be set to a 3rd SMTP domain that is only being used by the 2010 environment
## This then gets set on the MEU in the source side

foreach ($item in $import){
 New-MoveRequest -Identity $item.smtp -domaincontroller $TargetDC -RemoteLegacy -RemoteGlobalCatalog $SourceDC -RemoteCredential $RemoteCredentials -TargetDeliveryDomain $TargetDeliveryDomain -baditemlimit 50
}
## clean up after yourself and close your remote powershell session
remove-pssession $ExchSession

stop-transcript

The move mailbox ends up being the easiest part of this whole process. You can run powershell commands to check the status of the move request or just go into the Exchange console and check it there.

Active Directory Migrations: Parameters file & Ping

So at this point in our continuing series we’ve generated our include files for the various steps we’re going to run today and we’ve also added DNS suffixes to our user workstations and rebooted them. Now we want to make sure that they’ve actually come back from the reboots.

You could do a simple command/batch file and ping them all and that might be the easier way to go, but I prefer powershell and since we’re already in powershell, why wouldn’t want to do it that way?

This is also the point where we want to make sure that our Powershell session contains all the variables we need for the rest of our scripts. There’s a ton of stuff that’s going on depending on who you’re moving, from what source/target environment you’re going to, etc. So rather than re-init those variables for each script it makes more sense to load them once and save them everywhere.

The first thing we want to do is open Powershell.

We need to launch this script as . .\params.p1  The extra dot space in front of it allows us to use these variables elsewhere. It’s actually pretty powerful. I liken it to old days of building web pages where we’d use an include file for using the same footer everywhere.


## filename params.ps1

## Set variables
## In this script I want to check to see if the variable is already loaded in memory (hence the if statements)
## If they are, ignore. If they're not, load them in

if (!$ImportFile){$ImportFile="D:\Migration\admtincludes\UserIncludes.csv"}
if (!$SourceDC){$SourceDc="SourceDC1.domain1.com"}
if (!$TargetDC){$TargetDC="TargetDC1.domain2.com"}

## Get credentials for both sides for the scripts
if (!$LocalCredentials){
 write-host "Input Target Creds" -backgroundcolor red -foregroundcolor white
 $LocalCredentials=get-credential}
if (!$RemoteCredentials){
 write-host "Input Source creds" -backgroundcolor red -foregroundcolor white
 $RemoteCredentials=get-credential}

if (!$TargetDeliveryDomain){$TargetDeliveryDomain="domain2.com"}
if (!$ExchURI){$ExchURI="http://TargetExchange.domain2.com/powershell"}
if (!$LyncURI){$LyncURI="https://TargetLyncPool.domain2.com/ocspowershell"}
if (!$LyncFEURI){$LyncFEURI="https://TargetLyncFe.domain2.com/ocspowershell"}
if (!$LyncFEUSourceRI){$LyncFESourceURI="https://SourceDomainLyncFE.domain.com/ocspowershell"}
if (!$LyncURISource){$LyncURISource="https://SourcedomainLyncDir.domain.com/ocspowershell"}
if (!$SourceDomain){$SourceDomain="domain1.com"}
if (!$TargetDomain){$TargetDomain="domain2.com"}
if (!$RegistrarPool){$RegistrarPool="lyncregistrar.domain2.com"}
if (!$TargetOU){$TargetOU="OU1/OU2"}
if (!$MasterGroup){$MasterGroup="d:\migration\mastergrouplist.txt"}
if (!$date){$date=get-date -format "yyyyMMdd"}
if (!$ComputerList){$ComputerList = Import-Csv "D:\Migration\admtincludes\BitLockerCompIncludes.csv"}
if (!$PrecheckCompList){$PrecheckCompList = import-csv "d:\migration\admtincludes\compincludes.csv"}

## Make sure you have the Quest AD tools installed

if ( (Get-PSSnapin -Name Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue) -eq $null )
{Add-PSSnapin Quest.ActiveRoles.ADManagement}

DNSPing.ps1


## File name dnsping.ps1
## Set date and transcript

$date=get-date -format "yyyyMMdd"
$Tranoutput="d:\migration\Outputs\" + $date + "Ping.txt"
start-transcript -path $Tranoutput

clear-host

## Similar to previous script, set array for machines you can't connect to
## Add them to the array

$NoPing=@()
$Computer=import-csv "d:\migration\admtincludes\CompIncludes.csv"
foreach ($comp in $computer){
 $ok=test-connection $comp.computer -count 1 -quiet
 write-host "Testing..." $comp.computer -foregroundcolor yellow
 if (-not($ok)){
 $noping+=$comp.computer
 }
}

## For each machine you can't ping, output to the host in a nice bright color that you couldn't connect to it
## In my case, of running migrations in the middle of the night, if I can't connect I remove them from the migration list and continue on.

foreach ($item in $NoPing){write-host "The following computers are not responding:" $item -backgroundcolor "red"}
if (!$NoPing){write-host "All computers responding" -foregroundcolor yellow}

stop-transcript

Active Directory Migrations: DNS Suffixes & Reboot

Part 2 of the continuing series of how to migrate users and computers in a domain migration.

This next script shouldn’t be necessary, if you’re managing your DNS suffixes correctly, using WINS, or don’t have a ton of DNS suffixes out there.

In this migration we’re turning off WINS as part of the migration. There literally dozens of possible DNS Suffixes users can have in their search order, and none of it is centrally managed. The preferred way to do this is to manager your suffix search order thru either GPO or DHCP options, but that wasn’t possible here. We also wanted to make sure that all of our user workstations were online prior to the migration and didn’t have any lingering issues that would prevent them from migrating, so wanted to be able to reboot the workstations first, before we did anything.

This script is actually doing a lot of stuff, and calls another script (pasted in below). Comments inline

# FileName: AppendToRegistry.ps1
# Purpose: To append a string value to a registry entry locally or remotely
# Hive valid values: "ClassesRoot","CurrentConfig","CurrentUser","DynData","LocalMachine","PerformanceData","Users"

## Set the date format and output files for our transcript
## Start transcript to log everything that happens on the screen.

$date=get-date -format "yyyyMMdd"
$Tranoutput="d:\migration\Outputs\" + $date + "DNSSuffix.txt"
start-transcript -path $Tranoutput

## DNS Suffixes we want to ensure get added to workstations
## This doesn't overwrite existing suffixes, but checks to see if these exist. If they do, cool.
## If they don't, add them

$domains = @("DOM1.COM","DOM2.com","DOM3.com")

## Include file generated previously that all the computer names we need to hit.
## Set array variables for our ping script so that we don't have timeouts connecting to machines that aren't up
$RegistryFile = "D:\Migration\admtincludes\CompIncludes.csv"
$NoPing=@()
$YesPing=@()

## Import our CSV file and for each item in the list do a test-connection (i.e. ping)
## If a machine can't be pinged (pung?), add it to the array
## Output the array to the screen in bright colors so that we know we can't connect to them.
## We need to know this so that we don't try to migrate that user or desktop

$NoPing=import-csv -path $Registryfile|where-object {-not (Test-Connection -count 1 -computername $_.computer -erroraction silentlycontinue)}
clear-host
if ($noping){foreach ($item in $NoPing){write-host "The following computers are not responding:" $item.computer -backgroundcolor "red"}}

## Set a new variable and try to ping them all again.
## This sets the array that we use later for actually setting registry and reboot

$YesPing=import-csv -path $Registryfile|where-object {(Test-Connection -count 1 -computername $_.computer -erroraction silentlycontinue)}

##Using the array of up machines, set variables for computers and reg keys

$YesPing| ForEach-Object {
 $computer = $_.Computer
 $hive = "LocalMachine"
 $key = "System\CurrentcontrolSet\services\tcpip\parameters"
 $property = "SearchList"

##Call the other powershell file Get-RemoteRegistry.ps1 and pass variables to it.

[string]$currentValue=./Get-RemoteRegistry -hive $hive -key $key -name $property -computername $computer
 $modified = $false
 $newvalue = [string]::Empty

## If there is no value, set the value to the three default domains
## I.e. if there was no DNS Suffixes set

if ([string]::IsNullOrEmpty($currentValue))  {
$newvalue = $domains -join ','
$modified = $true}
 else {
## if there suffixes set
$domains | ForEach-Object {
## convert everything to lowercase
## set new values comma delimited, set modified variable to true
            if ($currentValue.ToLower().IndexOf($_) -lt 0) {
$newvalue = $newvalue + "," + $_
$modified = $true}
}

## Add current value and new value together, comma delimited
if ($modified){$newvalue = $currentValue + $newvalue}
 }
 # Check to see if the value was modified
# Call set-remoteregistry.ps1 and pass variables
 if ($modified)
 {
 ./Set-RemoteRegistry -key $key -name $property -value $newvalue -type string -computername $computer –force
 write-host "DNS Registry set on: " $computer
 }

## Reboot workstation
 restart-computer $computer -force
 write-host $Computer " is being rebooted"

}
stop-transcript

Get-RemoteRegistry.ps1:


[CmdletBinding(SupportsShouldProcess=$true)]
param
(
 [Parameter(Position=0, Mandatory=$false)]
 [System.String]
 $ComputerName = $Env:COMPUTERNAME,
 [Parameter(Position=1, Mandatory=$false)]
 [ValidateSet("ClassesRoot","CurrentConfig","CurrentUser","DynData","LocalMachine","PerformanceData","Users")]
 [System.String]
 $Hive = "LocalMachine",
 [Parameter(Position=2, Mandatory=$true, HelpMessage="Enter Registry key in format System\CurrentControlSet\Services")]
 [ValidateNotNullOrEmpty()]
 [System.String]
 $Key,
 [Parameter(Position=3, Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [System.String]
 $Name
)

#Open remote registry
try
{
 $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $ComputerName)

}
catch
{

Write-Error "The computer $ComputerName is inaccessible. Please check computer name. Please ensure remote registry service is running and you have administrative access to $ComputerName."
 Return
}

$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $ComputerName)
$regkey = $reg.OpenSubkey($Key)
return $regkey.GetValue($Name)

Set-RemoteRegistry.ps1:


<#
 .SYNOPSIS
 Set-RemoteRegistry allows user to set any given registry key/value pair.

.DESCRIPTION
 Set-RemoteRegistry allows user to change registry on remote computer using remote registry access.

.PARAMETER ComputerName
 Computer name where registry change is desired. If not specified, defaults to computer where script is run.

.PARAMETER Hive
 Registry hive where the desired key exists. If no value is specified, LocalMachine is used as default value. Valid values are: ClassesRoot,CurrentConfig,CurrentUser,DynData,LocalMachine,PerformanceData and Users.

.PARAMETER Key
 Key where item value needs to be created/changed. Specify Key in the following format: System\CurrentControlSet\Services.

.PARAMETER Name
 Name of the item that needs to be created/changed.

 .PARAMETER Value
 Value of item that needs to be created/changed. Value must be of correct type (as specified by -Type).

 .PARAMETER Type
 Type of item being created/changed. Valid values for type are: String,ExpandString,Binary,DWord,MultiString and QWord.

 .PARAMETER Force
 Allows user to bypass confirmation prompts.

 .EXAMPLE
 PS C:\> .\Set-RemoteRegistry.ps1 -Key SYSTEM\CurrentControlSet\services\AudioSrv\Parameters -Name ServiceDllUnloadOnStop -Value 1 -Type DWord

.EXAMPLE
 PS C:\> .\Set-RemoteRegistry.ps1 -ComputerName ServerA -Key SYSTEM\CurrentControlSet\services\AudioSrv\Parameters -Name ServiceDllUnloadOnStop -Value 0 -Type DWord -Force

.INPUTS
 System.String

.OUTPUTS
 System.String

.NOTES
 Created and maintainted by Bhargav Shukla (MSFT). Please report errors through contact form at http://blogs.technet.com/b/bshukla/contact.aspx. Do not remove original author credits or reference.

.LINK
 http://blogs.technet.com/bshukla
#>
 [CmdletBinding(SupportsShouldProcess=$true)]
 param
 (
 [Parameter(Position=0, Mandatory=$false)]
 [System.String]
 $ComputerName = $Env:COMPUTERNAME,
 [Parameter(Position=1, Mandatory=$false)]
 [ValidateSet("ClassesRoot","CurrentConfig","CurrentUser","DynData","LocalMachine","PerformanceData","Users")]
 [System.String]
 $Hive = "LocalMachine",
 [Parameter(Position=2, Mandatory=$true, HelpMessage="Enter Registry key in format System\CurrentControlSet\Services")]
 [ValidateNotNullOrEmpty()]
 [System.String]
 $Key,
 [Parameter(Position=3, Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [System.String]
 $Name,
 [Parameter(Position=4, Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [System.String]
 $Value,
 [Parameter(Position=5, Mandatory=$true)]
 [ValidateSet("String","ExpandString","Binary","DWord","MultiString","QWord")]
 [System.String]
 $Type,
 [Parameter(Position=6, Mandatory=$false)]
 [Switch]
 $Force
 )

 If ($pscmdlet.ShouldProcess($ComputerName, "Open registry $Hive"))
 {
 #Open remote registry
 try
 {
 $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $ComputerName)

 }
 catch
 {

Write-Error "The computer $ComputerName is inaccessible. Please check computer name. Please ensure remote registry service is running and you have administrative access to $ComputerName."
 Return
 }
 }

If ($pscmdlet.ShouldProcess($ComputerName, "Check existense of $Key"))
 {
 #Open the targeted remote registry key/subkey as read/write
 $regKey = $reg.OpenSubKey($Key,$true)

 #Since trying to open a regkey doesn't error for non-existent key, let's sanity check
 #Create subkey if parent exists. If not, exit.
 If ($regkey -eq $null)
 {
 Write-Warning "Specified key $Key does not exist in $Hive."
 $Key -match ".*\x5C" | Out-Null
 $parentKey = $matches[0]
 $Key -match ".*\x5C(\w*\z)" | Out-Null
 $childKey = $matches[1]

try
 {
 $regtemp = $reg.OpenSubKey($parentKey,$true)
 }
 catch
 {
 Write-Error "$parentKey doesn't exist in $Hive or you don't have access to it. Exiting."
 Return
 }
 If ($regtemp -ne $null)
 {
 Write-Output "$parentKey exists. Creating $childKey in $parentKey."
 try
 {
 $regtemp.CreateSubKey($childKey) | Out-Null
 }
 catch
 {
 Write-Error "Could not create $childKey in $parentKey. You may not have permission. Exiting."
 Return
 }

$regKey = $reg.OpenSubKey($Key,$true)
 }
 else
 {
 Write-Error "$parentKey doesn't exist. Exiting."
 Return
 }
 }

 #Cleanup temp operations
 try
 {
 $regtemp.close()
 Remove-Variable $regtemp,$parentKey,$childKey
 }
 catch
 {
 #Nothing to do here. Just suppressing the error if $regtemp was null
 }
 }

 #If we got this far, we have the key, create or update values
 If ($Force)
 {
 If ($pscmdlet.ShouldProcess($ComputerName, "Create or change $Name's value to $Value in $Key. Since -Force is in use, no confirmation needed from user"))
 {
 $regKey.Setvalue("$Name", "$Value", "$Type")
 }
 }
 else
 {
 If ($pscmdlet.ShouldProcess($ComputerName, "Create or change $Name's value to $Value in $Key. No -Force specified, user will be asked for confirmation"))
 {
 $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes",""
 $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No",""
 $choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
 $caption = "Warning!"
 $message = "Value of $Name will be set to $Value. Current value `(If any`) will be replaced. Do you want to proceed?"
 Switch ($result = $Host.UI.PromptForChoice($caption,$message,$choices,0))
 {
 1
 {
 Return
 }
 0
 {
 $regKey.Setvalue("$Name", "$Value", "$Type")
 }
 }
 }
 }

 #Cleanup all variables
 try
 {
 $regKey.close()
 Remove-Variable $ComputerName,$Hive,$Key,$Name,$Value,$Force,$reg,$regKey,$yes,$no,$caption,$message,$result
 }
 catch
 {
 #Nothing to do here. Just suppressing the error if any variable is null
 }

Active Directory Migrations: Generating Files

So as you can probably tell from my last few posts I’ve been embroiled in an AD forest to forest migration for the past little while (and a little while longer). I have approximately 3000 users to migrate and am responsible for the mailbox move, user move, computer move, groups moves, Lync move, etc, etc. Basically I’ve got everything that makes a user a user.

I’ve scripted all of it and it took a ton of hours to get the scripts working right and to get all the little tweaks in there. It’s working pretty smoothly right now and I’m going to impart my wisdom in the next series of posts. My first bit of wisdom is to stay away from these types of migrations if you can help it 🙂 They’re a pain and there are so many moving parts that it’s difficult to get everything working just quite right.

But now that you’ve been told you just gotta do this, let me help where I can.

This first script is what I call my master script. I have the PM’s generating what I call the gold sheet for every migration that we do. In it is every field I need for every single script that I run. What this script here does is take the CSV file I get from them and generate all the various include files I need for the other scripts to run (some can’t take the massive initial file).

Modify this for your needs:

## Get the date in a format we want
## Set our transcript file and start the transcript so we have a log

$date=get-date -format "yyyyMMdd"
$Tranoutput="d:\migration\Outputs\" + $date + "ADMTIncludes.txt"
start-transcript -path $Tranoutput

## Set output variables. Declare them as arrays
$output=@()

$compOutput=@()
$ADMTCompOutput=@()
$BLCompOutput=@()

## Import our initial include file for all the users we're migrating today
## For each item in the include file, pull some of the variables and set them in the multi-column array
## Write them to an output array
## Set our various arrays to the output format

import-csv "d:\migration\admtincludes\Userincludes.csv"| ForEach-Object {
 $obj = New-Object PSObject | Select-Object SourceName, TargetRDN, TargetUPN
 $obj.SourceName = $_.sourceName
 $obj.TargetRDN = $_.TargetRDN
 $obj.TargetUPN = $_.TargetUPN
 $output += $obj

$CompObj=new-object PSObject|select-object Computer
 $CompObj.Computer=$_.computer
 $CompOutput+=$compobj

$ADMTCompObj="DOMAINNAME\"+$_.computer
 $ADMTCompOutput+=$ADMTcompobj

$ADMTCompObj2="DOMAINNAME\"+$_.computer
 $ADMTCompOutput2+=$ADMTcompobj2+";"
 $BLCompObj=new-object PSObject|select-object Computer
 $BLCompObj.Computer=$_.computer+".DOMAIN.COM"
 $BLCompOutput+=$BLcompobj

}

## Take all of our arrays we just populated and export them to a standard output filename
## all the other scripts look for these files in this format
## A couple are exported as CSV's, some are outputted as text files. Note the difference

write-host "...Generating Files..." -foregroundcolor red
$output|export-csv d:\migration\admtincludes\$date"ADMTMigration.csv" -notypeinformation
$CompOutput|export-csv d:\migration\admtincludes\CompIncludes.csv -notypeinformation
$ADMTCompOutput|out-file d:\migration\admtincludes\ADMTCompIncludes.txt
$ADMTCompOutput2|out-file d:\migration\admtincludes\ADMTCompMigIncludes.txt
$BLCompOutput|export-csv d:\migration\admtincludes\BitLockerCompIncludes.csv -notypeinformation

## Stop our screen transcript and read the log if necessary

stop-transcript

Powershell: using datetime

So let’s assume that in previous code I’ve pulled a list of all users and groups and now want to run some code daily to get new users and groups. You don’t want to pull the whole list every day, as this would get a little unwieldy. But if you just want to know what in AD has changed in the past day or past week or whatever, this is a handy snippet of code that can get you there.

Datetime is always a little tricky to manipulate, but it’s very powerful if you want to do comparisons.


$dayEnd = [datetime]::Today
$dayStart = $dayEnd.AddDays(-1)
$results=@()
foreach ($item in $(get-distributiongroup -resultsize unlimited|where-object {$_.whencreated -ge $daystart -and $_.whencreated -lt $dayend})){$results+=$item.name}

  • $dayEnd = [datetime]::Today <== Here I’m just creating a new variable and want to set it to datetime. But specifying [datetime]::Today I’m telling it just give me monthy, day and year, so it would set the variable to 02262013 00:00:00, for example. Handy since I don’t want it to give a time of NOW.
  • $dayStart = $dayEnd.Adddays(-1) <== So take the previous variable and subtract 1 day from it (i.e. if $dayEnd was 02262013 00:00:00, $dayStart would be 02252013 00:00:00). If you wanted it to add 3 days to the starting variable you’d just do .Adddays(3). A minus sign is a negative here, obviously. So if you wanted the last week you’d do .Adddays (-7).  If you didn’t care about comparing the 2 dates you could also set the initial variable to $day=[datetime]::Today.AddDays(-7) to set $day to a week ago
  • $results=@() <== Create and set an array into a variable
  • get-distributiongroup -resultsize unlimited|where-object {$_.whencreated -ge $daystart -and $_.whencreated -lt $dayend} <== I tried doing a -filter, but that didn’t work correctly, so doing a  pipe into a where clause and key off of whencreated and greater than or equal to the $daystart and less than the $dayend.

Powershell: List all Mailboxes and Export

In a previous post I covered how to do this with Distribution Lists. Now I want to do the same thing for user mailboxes, however the code ended up being a little bit different since I want to key off of Primary SMTP Address vs. just the name. Since the email address has an @ sign in it, the code changes because it exports differently.

This particular code snippet gets all Mailboxes in Exchange and dumps them to a file. Easily modifiable for other purposes if you need to:

$results=@()
$results+="smtp"
foreach ($item in $(get-mailbox -resultsize unlimited)){$results+=[string]$item.PrimarySMTPAddress}
$results|out-file c:\migration\userout.txt

Let’s break this down a little bit:

$results=@() <== explicitly creates an array for population later

$results+=”smtp” <== because of what I want to do with the export file later, I want a header in this file. This line just creates the initial header.

foreach ($item in $(get-mailbox -resultsize unlimited)){$results+=[string]$item.PrimarySMTPAddress} <== Several things going on in this line. I like combining statements into as little possible code as I can, so this one can be confusing if you don’t know how to look at it

  • foreach (blah in blah){blah} <==typical for each statement that does the {} for every item in the list
  • $item in $(get-mailbox -resultsizeunlimited) <== Pulls every mailbox with no limitations and puts them into the array

$results+=[string]$item.PrimarySMTPAddress <== Pulls the Primary SMTP attribute from every mailbox and adds it into the array defined above. Had to convert it to a string so that it would export in the correct format

$results|out-file filename.txt <== Outputs the results of the array into a textfile