Monday, February 4, 2013

When you logon to Facebook....


When you logon to facebook you get the question: "How is it going, Bas?"...
Today I felt like answering the question. Below my answer.
Dear Facebook,

Thank you for asking... I am currently recuperating from a very hectic weekend.

I started the final migration of a dutch company on friday. It was the end of 4 months preparation. But although we didn't invite Murphy, he decided to show up anyways...
For the past 4 months our migration efforts were brought back to 7 or 8 scripts running at scheduled intervals. Daily manual checks were done of the logfiles and runtimes and we were very confident that the migration would be a success.

And there was Murphy and his Law (The prick)
Everything that could go wrong went wrong. Scripts failed to complete due to offline databases, disabled users, and rebooting servers.... (Who forgot to include the support team to suspend the maintenance windows)
Nothing is more frustrating than working on fixing a script to adapt to the new situation, having found the solution and than some ass decides to reboot the server you are working on...

So friday I clocked a 14 hour workday (hours after 12 are on saturday) to have finally migrated everything.... As that was what we believed... I was glad to have taken an hour of to visit Tian Dao to be treated for my high blood pressure and that was very relaxing... Thanks for that...

Saturday morning after about 3 hours sleep I started to check the logfiles. And found that all the steps are completed successful. Ahhhh Weekend was the thought.. and queue Murphy..

In this company there are some strict naming rules for users.... Which are not followed of-course. So when you want to migrate Jan Jansen you look for j.jansen conform the naming standards... And migrate someone totally different since the logon name for jan was JanJ... And you wonder why you cannot find him...

So after another long day (again 14 hours on the clock) we check into bed... for a nights rest... And a good night rest it was..

Sunday, fun day... Yeah.. last day of migration.. In the input list we found that several users were not given an email forward address to the new mail. and since a script flawless processes what you input the mail for those users were not forwarded.. Works as designed, but not as desired... So the torubleshooting starts... and remigrating those accounts.. But I managed to make it a shorter day, and after sleeping in I only managed to clock 8 hours of after care this day..

After a handover to my colleague for the aftercare on monday I went to bed for a good nights rest....

And a good nights rest it was...

So to answer your question Facebook. I am doing alright. A bit tired but fulfilled with a job well done...

So and now back to work and finish these reports which are due..

Goodday,

Bas Steelooper

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