Created
July 31, 2014 16:02
-
-
Save meitinger/8d1f90ac19556e1bce33 to your computer and use it in GitHub Desktop.
Checks if a Outlook profile file (*.prf) matches the current profile and re-creates it if necessary.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param([Parameter(Mandatory=$true)]$profileIni,[switch]$cleanPSTs) | |
# registry value mappings | |
$accountGuids = @{ | |
IMAP = '{ed475412-b0d6-11d2-8c3b-00104b2a6676}' | |
POP3 = '{ed475411-b0d6-11d2-8c3b-00104b2a6676}' | |
HTTP = '{4db5cbf0-3b77-4852-bc8e-bb81908861f3}' | |
} | |
$mappings = @{ | |
PT_STRING8 = @{ | |
FromService = { | |
param([Parameter(Mandatory=$true)][AllowEmptyString()][string]$string) | |
return $string | |
} | |
FromConfig = { | |
param([Parameter(Mandatory=$true)][AllowEmptyString()][string]$string) | |
return [Environment]::ExpandEnvironmentVariables($string) | |
} | |
Value = 0x001e | |
} | |
PT_BOOLEAN = @{ | |
FromService = { | |
param([Parameter(Mandatory=$true)][ValidateCount(2, 2)][byte[]]$binary) | |
return [bool]($binary[0]+$binary[1]*256) | |
} | |
FromConfig = { | |
param([Parameter(Mandatory=$true)][string]$string) | |
switch ($string) | |
{ | |
'true' { return $true } | |
'yes' { return $true } | |
'false' { return $false } | |
'no' { return $false } | |
default { throw New-Object System.FormatException } | |
} | |
} | |
Value = 0x000b | |
} | |
PT_LONG = @{ | |
FromService = { | |
param([Parameter(Mandatory=$true)][ValidateCount(4, 4)][byte[]]$binary) | |
return [int]($binary[0] + $binary[1]*256 + $binary[2]*256*256 + $binary[3]*256*256*256) | |
} | |
FromAccount = { | |
param([Parameter(Mandatory=$true)][int]$dword) | |
return $dword | |
} | |
FromConfig = { | |
param([Parameter(Mandatory=$true)][string]$string) | |
return [int]$string | |
} | |
Value = 0x0003 | |
} | |
PT_UNICODE = @{ | |
FromService = { | |
param([Parameter(Mandatory=$true)][byte[]]$binary) | |
return [System.Text.Encoding]::Unicode.GetString($binary).TrimEnd("`0") | |
} | |
FromAccount = { | |
param([Parameter(Mandatory=$true)][byte[]]$binary) | |
return [System.Text.Encoding]::Unicode.GetString($binary).TrimEnd("`0") | |
} | |
FromConfig = { | |
param([Parameter(Mandatory=$true)][AllowEmptyString()][string]$string) | |
return [Environment]::ExpandEnvironmentVariables($string) | |
} | |
Value = 0x001f | |
} | |
} | |
# read the profile ini file | |
function ParseProfileIni | |
{ | |
$ini = @{} | |
$section = ''; | |
$ini[$section] = @{} | |
switch -regex -file $profileIni | |
{ | |
'^\s*(;.*)?$' | |
{ | |
continue | |
} | |
'^\s*\[\s*(.+?)\s*\]\s*$' | |
{ | |
$section = $matches[1] | |
$ini[$section] = @{} | |
continue | |
} | |
'^\s*(.*?)\s*=\s*(.*?)\s*$' | |
{ | |
$name,$value = $matches[1..2] | |
$ini[$section][$name] = $value | |
continue | |
} | |
default | |
{ | |
throw 'invalid profile file format' | |
} | |
} | |
return $ini | |
} | |
# checks all internet accounts or service accounts within a given section | |
function CheckAccounts | |
{ | |
param | |
( | |
[Parameter(Mandatory=$true)][string]$section, | |
[Parameter(Mandatory=$true)][bool]$isService | |
) | |
# go over all instances | |
foreach ($name in $ini[$section].Keys) | |
{ | |
# get the instance and definition entries | |
$typeName = $ini[$section][$name] | |
$definition = $ini[$typeName] | |
$instance = $ini[$name] | |
# set all variables that differ between account and service types | |
if ($isService) | |
{ | |
$filter = { | |
$_.GetValue('clsid') -eq '{ed475414-b0d6-11d2-8c3b-00104b2a6676}' -and | |
(SaveStringFromBinary($_.GetValue('Service Name'))) -eq $definition['ServiceName'] | |
} | |
$defineValueName = { | |
$valueName = ($mapping['Value']*65536+[int]$parts[1]).ToString('x8') | |
} | |
$accountSource = 'FromService' | |
$excludeMismatch = 'ServiceName' | |
} | |
else | |
{ | |
$filter = { $_.GetValue('clsid') -eq $accountGuids[$definition['AccountType']] } | |
$defineValueName = { | |
if ($_.StartsWith('_')) { $valueName = $_.Substring(1) } | |
else { $valueName = $_ } | |
} | |
$accountSource = 'FromAccount' | |
$excludeMismatch = 'AccountType' | |
} | |
# filter out all account keys that do not match the required type | |
$accounts = @($allAccounts | ? $filter) | |
# check the uniqueness | |
if ($instance['UniqueService'] -eq 'yes' -and $accounts.Length -gt 1 ) { CreateProfile "more than one $typeName exist" } | |
# check how many accounts match the current instance | |
$count = 0 | |
$closestMismatch = $null | |
foreach ($account in $accounts) | |
{ | |
# translate the key if necessary | |
if ($isService) | |
{ | |
$serviceName = SaveStringFromBinary($account.GetValue('Account Name')) | |
$serviceKey = OpenReferenceKey $account $serviceName 'Service UID' 'service key' | |
if ((SaveStringFromBinary($account.GetValue('Service Name'))) -eq 'MSUPST MS') | |
{ | |
$serviceKey = OpenReferenceKey $serviceKey $serviceName '01023d00' 'profile key' | |
} | |
$account = $serviceKey | |
} | |
# find all entries that do not match the ones of the current instance | |
$mismatchEntries = @($instance.Keys | ? { | |
# get the entry type, id and value | |
if ($_ -eq 'UniqueService') { return $false } | |
$parts = $definition[$_] -split ',' | |
$mapping = $mappings[$parts[0]] | |
$configValue = &$mapping['FromConfig']($instance[$_]) | |
.$defineValueName | |
# query the existing registry value | |
$regValue = $account.GetValue($valueName) | |
if ($regValue -eq $null) { return $true } | |
try { $regValue = &$mapping[$accountSource]($regValue) } | |
catch { return $true } | |
# return true on mismatch | |
return $configValue -ne $regValue | |
}) | |
# add all entries that haven't been set but exist in the registry | |
$mismatchEntries += @($definition.Keys | ? { | |
if ($_ -eq $excludeMismatch -or $instance.Keys -contains $_) { return $false } | |
$parts = $definition[$_] -split ',' | |
$mapping = $mappings[$parts[0]] | |
.$defineValueName | |
return $account.GetValue($valueName) -ne $null | |
}) | |
# increment the instance counter and set the closest mismatch string | |
if (-not ($mismatchEntries -like '_*')) | |
{ | |
$count = $count + 1 | |
if ($mismatchEntries) | |
{ | |
$closestMismatch = $mismatchEntries -join ', ' | |
} | |
} | |
} | |
# (re)create the profile if necessary | |
switch ($count) | |
{ | |
0 { CreateProfile "$name doesn't exist" } | |
1 { if ($closestMismatch -ne $null) { CreateProfile "$name differs in $closestMismatch" } } | |
default { CreateProfile "multiple $name exist" } | |
} | |
} | |
} | |
# convert a registry binary savely to string | |
function SaveStringFromBinary | |
{ | |
param([Parameter(Mandatory=$true)][AllowNull()]$binary) | |
if (-not ($binary -is [byte[]])) { return [string]::Emtpy } | |
return [System.Text.Encoding]::Unicode.GetString($binary).TrimEnd("`0") | |
} | |
# opens either the service key or the backup key | |
function OpenReferenceKey | |
{ | |
param | |
( | |
[Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$key, | |
[Parameter(Mandatory=$true)][string]$accountName, | |
[Parameter(Mandatory=$true)][string]$referenceName, | |
[Parameter(Mandatory=$true)][string]$referenceType | |
) | |
$reference = $key.GetValue($referenceName) -as [byte[]] | |
if (-not $reference) { CreateProfile "account '$accountName' has no $referenceType reference" } | |
$path = Join-Path $rootKey (($reference | % { $_.ToString('x2') }) -join '') | |
if (-not (Test-Path $path -PathType Container)) { CreateProfile "$referenceType of account '$accountName' is missing" } | |
return Get-Item $path | |
} | |
# check if all PST files exists and return a list of them | |
function RetrieveAndCheckAllPSTs | |
{ | |
# get all pst accounts | |
$psts = @() | |
$allServices = @(Get-ChildItem (Join-Path $rootKey '*')) | |
foreach ($service in $allServices) | |
{ | |
# get the path | |
$pstPath = $service.GetValue('001f6700') -as [byte[]] | |
if (-not $pstPath) { continue } | |
$pstPath = [System.Text.Encoding]::Unicode.GetString($pstPath).TrimEnd("`0") | |
$pstName = SaveStringFromBinary($service.GetValue('001f3001')) | |
# ensure it exists (if it is a PST) and add it to the list | |
if ((SaveStringFromBinary($service.GetValue('001f3d09'))) -eq 'MSUPST MS' -and -not (Test-Path $pstPath -PathType Leaf)) { CreateProfile "file for PST account '$pstName' is missing ($pstPath)" } | |
$psts += $pstPath | |
} | |
return $psts | |
} | |
# remove all unused PST files | |
function CleanupUnusedPSTs | |
{ | |
param([Parameter(Mandatory=$true)][string[]]$psts) | |
if (Test-Path $rootPath -PathType Container) | |
{ | |
Get-ChildItem (Join-Path $rootPath '*.pst') | ? { $psts -notcontains $_.FullName } | % { | |
$_.Delete() | |
$Session.Information("Deleted unused PST file: $_") | |
} | |
} | |
} | |
# function that is called to (re)create the profile and exit the script | |
function CreateProfile | |
{ | |
param([Parameter(Mandatory=$true)][string]$reason) | |
$Session.Warning("Recreate profile because $reason.") | |
# close or kill Outlook if it's running | |
$sessionId = (Get-Process -id $pid).SessionId | |
Get-Process -Name 'Outlook' | ? { $_.SessionId -eq $sessionId } | % { | |
if (-not $_.CloseMainWindow()) { $_.Kill() } | |
$_.WaitForExit() | |
} | |
# clean up all old PST files if requested | |
if ($cleanPSTs) | |
{ | |
$psts = @($ini['Service List'].GetEnumerator() | ? { $ini[$_.Value]['ServiceName'] -eq 'MSUPST MS' } | % { | |
$definition = $ini[$_.Value] | |
$ini[$_.Key].GetEnumerator() | ? { | |
$parts = $definition[$_.Key] -split ',' | |
$parts[0] -eq 'PT_UNICODE' -and [int]$parts[1] -eq 0x6700 | |
} | % { [Environment]::ExpandEnvironmentVariables($_.Value) } | |
}) | |
CleanupUnusedPSTs $psts | |
} | |
# delete all profile-related files | |
$appDataPath = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::ApplicationData)) 'Microsoft\Outlook' | |
if (Test-Path $appDataPath) | |
{ | |
Remove-Item (Join-Path $appDataPath "$profileName.*") | |
Remove-Item (Join-Path $appDataPath "$profileName~*.*") | |
} | |
# launch Outlook and exit | |
$Session.Action = 'Geben Sie das Passwort ein und beenden Sie anschließend Outlook...' | |
Start-Process outlook.exe ('/importprf',(Convert-Path $profileIni)) -Wait | |
exit | |
} | |
# check if the PST path is set by GPO | |
$rootPath = [Microsoft.Win32.Registry]::GetValue('HKEY_CURRENT_USER\Software\Policies\Microsoft\Office\12.0\Outlook', 'ForcePSTPath', $null) | |
if ($rootPath -is [string]) | |
{ | |
# if so, expand all environment vars in it and make it absolute if necessary | |
$rootPath = [Environment]::ExpandEnvironmentVariables($rootPath) | |
if (-not [System.IO.Path]::IsPathRooted($rootPath)) | |
{ | |
$rootPath = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::Personal)) $rootPath | |
} | |
} | |
else | |
{ | |
# if no GPO defines the path use the default one | |
$rootPath = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)) 'Microsoft\Outlook' | |
} | |
# read the profile ini | |
$ini = ParseProfileIni | |
# check if the root key exists | |
$profileName = $ini['General']['ProfileName'] | |
$rootKey = Join-Path 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\' $profileName | |
if (-not (Test-Path $rootKey -PathType Container)) { CreateProfile "profile $profileName doesn't exist" } | |
$allAccounts = @(Get-ChildItem (Join-Path $rootKey '9375CFF0413111d3B88A00104B2A6676\*')) | |
# check all services | |
CheckAccounts 'Service List' $true | |
# check all internet accounts | |
CheckAccounts 'Internet Account List' $false | |
# get all personal folders and remove all unused PST files if requested | |
$psts = RetrieveAndCheckAllPSTs | |
if ($cleanPSTs) { CleanupUnusedPSTs $psts } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment