Friday, January 25, 2013

In the pocket: Core Solutions of MS Exchange Server 2013 Customer Preview

Today I saw that another IT pro passed his exam and I thought about the beta exams I did for Microsoft Exchange 2013...

So I took al little trip to https://www.register.prometric.com/CandidateHistory.asp and found out that I passed the "Core Solutions of MS Exchange Server 2013 Customer Preview" (071-341) exam... To bad that I failed the "Advanced Solutions of MS Exch Serv 2013 Customer Preview" (070-342) exam..

But with the lack of experience I was able to obtain since I couldn't install Exchange 2013 in my Exchange 2010 environment I think I did rather well with only reading about all there is on Technet and other Tech blogs...

I hope to find the time to install Exchange 2013 soon in my environment and update all the scripts to this version...

Monday, January 21, 2013

Exporting mailboxes to PST-files the easy way

When doing a cross domain migration it can be usefull to export the mailboxes to a PST file.

When doing this for a large amount of mailboxes it can be useful to use a script to automate this...

As I am lazy, I searched the internet for such a script, and found one from Steve Goodman which was close to what I wanted.. But as needy as I am, I needed more.

I modified, and added to the script which you find below. And is is as good as I am going to make it. It works like a charm (as I do say so myself)

Also made it so that if you are going to export a lot of mailboxes the target share and the source servers won't die on you you can set the maximum concurrent to a number your environment will survive.
The script will start the first batch, wait until completion, and than start the second batch.

When finished it will write some info to the report folder, and the logs for the completed and the failed requests...

Thanks go to Jeff Wouters which reviewed the script for me. And to his blog for the Add-Module function, which gave me a good idea for an add-SnapIN function which does basically the same but than for SnapIns.. so if you run the script multiple times you won't get an error :-)

# Exchange 2010 SP1 Mailbox Export Script
# Original script from : Steve Goodman (http://www.stevieg.org/2010/07/using-the-exchange-2010-sp1-mailbox-export-features-for-mass-exports-to-pst/)
# input from Jeff Wouters ( www.jeffwouters.nl)
# deviations from original script:
# - option to export all in the organisation
# - option to export all in a list
# - option to include archive mailboxes
# - option to exclude dumpster
# - option to set a maximum concurrent exports
# - no need to run from Exchange PowerShell. Needed library is loaded at runtime
$starttime = get-date -displayhint time
###############
# Settings    #
###############

# Pick ONE of the two below. If you choose both, it will use $Server.
$Server
$Database
$list = "\\server\share\users1.csv"

# Share to export mailboxes to. Needs R/W by Exchange Trusted Subsystem
# Must be a UNC path as this is run by the CAS MRS service.
$ExportShare = "\\server\share\pst\"
if (!(test-path -path $ExportShare -pathtype container))
{
 mkdir $ExportShare
}

# After each run a report of the exports can be dropped into the directory specified below. (The user that runs this script needs access to this share)
# Must be a UNC path or the full path of a local directory.
$ReportShare = "\\server\share\log\"
if (!(test-path -path $ReportShare -pathtype container))
{
 mkdir $ReportShare
}

# Shall we remove the PST file, if it exists beforehand? (The user that runs this script needs access to the $ExportShare share)
# Valid values: $true or $false
$RemovePSTBeforeExport = $true

# Do we want to include Archive Mailboxes
$IncludeArchive = $true

# Do we want to exclude dumpster
# currently not working as intended. workarround in place
$ExcludeDumpster = $false

# How many concurrent exports do we want?
# This must be an even number (2, 4, 6, 50, 100, 200, 1000 etc)
$maxConcurrentExports = 200

###############
# Code        #
###############

# function created by Jeff Wouters (www.jeffwouters.nl)
function Check-LoadedModule
{
  Param( [parameter(Mandatory = $true)][alias("Module")][string]$ModuleName)
  $LoadedModules = Get-Module | Select Name
  if (!$LoadedModules -like "*$ModuleName*") {Import-Module -Name $ModuleName}
}

# Deviation from Jeff Wouters function but for PSAddins
function Check-LoadedSnapIN
{
  Param( [parameter(Mandatory = $true)][alias("SnapIN")][string]$SnapINName)
 if ( (Get-PSSnapin -Name $SnapINName -ErrorAction SilentlyContinue) -eq $null )
 {
     Add-PsSnapin $SnapINName
 }
}

# If Archives are included we get 2 exports at a time. so we need to divide the maximum by 2
if ($includeArchive){
 $maxConcurrentExports = $maxConcurrentExports / 2
}

# load the Exchange powershell snapin if not loaded
Check-LoadedSnapIN Microsoft.Exchange.Management.PowerShell.E2010

# this function will create the MailboxExportRequests.
function exportMailbox ([String]$CurrentUser) {
     if ($RemovePSTBeforeExport -eq $true -and (Get-Item "$($ExportShare)\$($CurrentUser).PST" -ErrorAction SilentlyContinue))
     { 
         Remove-Item "$($ExportShare)\$CurrentUser.PST" -Confirm:$false -ErrorAction SilentlyContinue
         Remove-Item "$($ExportShare)\$CurrentUser-Archive.PST" -Confirm:$false -ErrorAction SilentlyContinue
     }
     if ($ExcludeDumpster){ # workarround if else. Would rather place this inline in the vode.
      New-MailboxExportRequest -BatchName $BatchName -Mailbox $CurrentUser -FilePath "$($ExportShare)\$CurrentUser.pst" -ExcludeDumpster
      if ($IncludeArchive){
       New-MailboxExportRequest -BatchName $BatchName -Mailbox $CurrentUser -FilePath "$($ExportShare)\$CurrentUser-Archive.pst" -IsArchive -ExcludeDumpster
      }
     } else {
      New-MailboxExportRequest -BatchName $BatchName -Mailbox $CurrentUser -FilePath "$($ExportShare)\$CurrentUser.pst"
      if ($IncludeArchive){
       New-MailboxExportRequest -BatchName $BatchName -Mailbox $CurrentUser -FilePath "$($ExportShare)\$CurrentUser-Archive.pst" -IsArchive
      }
  }     
}

# This function makes the script wait for completion of all exports.
function waitForCompletion {
 while ((Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Queued" -or $_.Status -eq "InProgress"}))
 {
     clear
     Write-Output "Waiting for the exports to complete"
     Get-MailboxExportRequest -BatchName $BatchName | Get-MailboxExportRequestStatistics | Where {$_.Status -eq "Queued" -or $_.Status -eq "InProgress"} | Out-Default | Format-Table
     sleep 60
 }
}

# Make batch name
$date=Get-Date
$BatchName = "Export_$($date.Year)-$($date.Month)-$($date.Day)_$($date.Hour)-$($date.Minute)-$($date.Second)"

if ($ExcludeDumpster){
 $additionalParm = "-ExcludeDumpster $true"
} else {
 $additionalParm = ""
}

# the collection of what to be export
if ($Server)
{
 write-output "Using Server"
    if (!(Get-ExchangeServer $Server -ErrorAction SilentlyContinue))
    {
        throw "Exchange Server $Server not found";
    }
    if (!(Get-MailboxDatabase -Server $Server -ErrorAction SilentlyContinue))
    {
        throw "Exchange Server $Server does not have mailbox databases";
    }
    $Mailboxes = Get-Mailbox -Server $Server -ResultSize Unlimited
} elseif ($Database) {
 write-output "Using database" 
    if (!(Get-MailboxDatabase $Database -ErrorAction SilentlyContinue))
    {
        throw "Mailbox database $Database not found"
    }
    $Mailboxes = Get-Mailbox -Database $Database -ResultSize Unlimited
} elseif ($list) {
 write-output "Using list: $list"
 $userlist = Import-CSV $list
} else {
    write-output "None of the above, so exporting all mailboxes.."
    $mailboxes = Get-Mailbox -ResultSize Unlimited
} 

# The Export
if ($list){
 Write-Output "Queuing $($userlist.Count) mailboxes as batch '$($BatchName)'"
 $teller = 0
 # Queue all mailbox export requests
 foreach ($user in $userlist)
 {
     $teller = $teller + 1
     $curuser =  $user.SamAccountName
     exportMailbox $curuser
     if ($teller -gt $maxConcurrentExports){
  Write-Output "Waiting for batch to complete"
  # Wait for mailbox export requests to complete
  waitForCompletion 
  $teller = 0
     }
 }  
 # Wait for mailbox export requests to complete
 waitForCompletion
}elseif ((!$Mailboxes) -and (!$Mailboxes.Count)) {
    throw "No mailboxes found on $Server or single Mailbox."
} else {
 Write-Output "Queuing $($Mailboxes.Count) mailboxes as batch '$($BatchName)'"
 # Queue all mailbox export requests
 $teller = 0
 foreach ($Mailbox in $Mailboxes)
 {
     $teller = $teller + 1
     exportMailBox $Mailbox.alias
     if ($teller -gt $maxConcurrentExports){
  Write-Output "Waiting for batch to complete"
  # Wait for mailbox export requests to complete
  waitForCompletion 
  $teller = 0
     }
 }
 # Wait for mailbox export requests to complete
 waitForCompletion
}

# Write reports if required
if ($ReportShare)
{
    Write-Output "Writing reports to $($ReportShare)"
    $Completed = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Completed"} | Get-MailboxExportRequestStatistics | Format-List
    if ($Completed)
    {
        $Completed | Out-File -FilePath "$($ReportShare)\$($BatchName)_Completed.txt"
    }
    $Incomplete = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -ne "Completed"} | Get-MailboxExportRequestStatistics
    if ($Incomplete)
    {
        $Incomplete | Format-List | Out-File -FilePath "$($ReportShare)\$($BatchName)_Incomplete_Report.txt"

  if (!$list){
   $list = "$($ReportShare)\$($BatchName)_failedpst.csv"
  }
  set-content -Value "Failed-PST" -Path $list".new"
  foreach ($woops in $Incomplete) {
   add-content -Value $woops.FilePath -Path $list".new"
  }

    }
}

$endtime = get-date -displayhint time

$runtimefile = "$($ReportShare)\$($BatchName)_runtime.txt"
$runtimeentry0 = $starttime.tostring() + ' - ' + $endtime.tostring()
$runtimeentry1 = "Completed exports: " + $completed.count
$runtimeentry2 = "Failedexports: " + $Incomplete.count
$runtimeentry3 = "Errored users:"
$runtimeentry4 = ( get-content -Path $list".new" | out-string)
Set-Content -Value $runtimeentry0 -Path $runtimefile
add-content -Value $runtimeentry1 -Path $runtimefile
add-content -Value $runtimeentry2 -Path $runtimefile
add-content -Value $runtimeentry3 -Path $runtimefile
add-content -Value $runtimeentry4 -Path $runtimefile

# Remove Requests
Write-Output "Removing requests created as part of batch '$($BatchName)'"
Get-MailboxExportRequest -BatchName $BatchName | Remove-MailboxExportRequest -Confirm:$false

Get Directory permission with powershell

One of my customers was facing a migration of their data to a new location. It was a cross domain migration so we don't want to take all the permissions with us in the migration (robocopy /DATS) and needed to find out the current situation.

Before I used the tool Security Explorer from Little Wonders.. And found out that through several take overs this is now Dell... (Scriptlogic -> Quest --> Dell)

since the software had gotten richer over the years with additional functions the price had also gone up, and is now only available in a time limited license..

So I thought, I can do this myself in PowerShell... And the result was very good (as I do say so myself)

I started out with the following script to get the permissions on the first 3 levels..
$OutFile = "C:\temp\folder-Permissions.csv"
$Header = "Folder Path,IdentityReference,AccessControlType,FileSystemRights,IsInherited"
Del $OutFile
Add-Content -Value $Header -Path $OutFile 

$RootPath = "z:\"

$Folders = get-CHildItem $RootPath | where {$_.psiscontainer -eq $true}

foreach ($Folder in $Folders){
 $ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access  }
 Foreach ($ACL in $ACLs){
  if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
  }else{
   $OutInfo = $Folder.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
   Add-Content -Value $OutInfo -Path $OutFile
  } 
 }
}

$Folders = get-CHildItem $RootPath\* | where {$_.psiscontainer -eq $true}

foreach ($Folder in $Folders){
 $ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access  }
 Foreach ($ACL in $ACLs){
  if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
  }else{
   $OutInfo = $Folder.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
   Add-Content -Value $OutInfo -Path $OutFile
  } 
 }
}

$Folders = get-CHildItem $RootPath\*\* | where {$_.psiscontainer -eq $true}

foreach ($Folder in $Folders){
 $ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access  }
 Foreach ($ACL in $ACLs){
  if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
  }else{
   $OutInfo = $Folder.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
   Add-Content -Value $OutInfo -Path $OutFile
  } 
 }
}

$Folders = get-CHildItem $RootPath\*\*\* | where {$_.psiscontainer -eq $true}

foreach ($Folder in $Folders){
 $ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access  }
 Foreach ($ACL in $ACLs){
  if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
  }else{
   $OutInfo = $Folder.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
   Add-Content -Value $OutInfo -Path $OutFile
  } 
 }
}

So since this worked, I started optimizing the code for posting on my blog.. and came up with the following. (Remember I want only the first 3 levels)

$OutFile = "C:\temp\folder-Permissions.csv"
$Header = "Folder Path,IdentityReference,AccessControlType,FileSystemRights,IsInherited"
Del $OutFile
Add-Content -Value $Header -Path $OutFile 

$RootPath = "z:\"

Function getSubFolderpermissions ($folders){
 foreach ($Folder in $Folders){
  $ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access  }
  Foreach ($ACL in $ACLs){
   if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
   }else{
    $OutInfo = $Folder.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
    Add-Content -Value $OutInfo -Path $OutFile
   } 
  }
 }
}


$Folders = get-CHildItem $RootPath | where {$_.psiscontainer -eq $true}
getSubFolderpermissions $folders

$Folders = get-CHildItem $RootPath\* | where {$_.psiscontainer -eq $true}
getSubFolderpermissions $folders

$Folders = get-CHildItem $RootPath\*\* | where {$_.psiscontainer -eq $true}
getSubFolderpermissions $folders

$Folders = get-CHildItem $RootPath\*\*\* | where {$_.psiscontainer -eq $true}
getSubFolderpermissions $folders


but still I didn't like this..

so after I little active rewriting I found this:
$OutFile = "C:\temp\folder-Permissions.csv"
$Header = "Folder Path,IdentityReference,AccessControlType,FileSystemRights,IsInherited"
remove-item $OutFile
Add-Content -Value $Header -Path $OutFile 

$RootPath = "\\san2\hdswbr$\Docs"
$levelsdeep = 3

Function getSubFolderpermissions ($Folders, $currentlevel){
 if ($currentlevel -lt ($levelsdeep + 1)){
  foreach ($map in $Folders){
   $subfolders = get-CHildItem $map.fullname | where {$_.psiscontainer -eq $true}
   if (!($subfolders -eq $null)){
    getSubFolderPermissions $subfolders ($currentlevel +1)
   }
   $ACLs = get-acl $map.fullname | ForEach-Object { $_.Access  }
   Foreach ($ACL in $ACLs){
    if ($ACL.IdentityReference -eq "BUILTIN\Administrators"){
    }else{
     $OutInfo = $map.Fullname + ";" + $ACL.IdentityReference  + ";" + $ACL.AccessControlType + ";" + $ACL.FileSystemRights + ";" + $ACL.IsInherited
     Add-Content -Value $OutInfo -Path $OutFile
    } 
   }
  }
 }
}

$Mappen = get-CHildItem $RootPath | where {$_.psiscontainer -eq $true}
getSubFolderpermissions $Mappen, 0