Skip to content

Instantly share code, notes, and snippets.

@meitinger
Created July 31, 2014 16:02
Show Gist options
  • Save meitinger/8d1f90ac19556e1bce33 to your computer and use it in GitHub Desktop.
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.
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