This repository has been archived by the owner on Dec 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Start-MailboxImport.ps1
397 lines (312 loc) · 13.7 KB
/
Start-MailboxImport.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
<#
.SYNOPSIS
Import one or more pst files into an exisiting mailbox or a archive
Thomas Stensitzki
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE
RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Version 2.0, 2023-05-04
.DESCRIPTION
This script importss one or more PST files into a user mailbox or a user archive as batch.
PST file names can used as target folder names for import. PST files are reanmed to support
filename limitations by New-MailboxImportRequest cmdlet.
.LINK
More information can be found at http://scripts.granikos.eu
.NOTES
Requirements
- Windows Server 2016+
- Exchange Server 2016+
Revision History
--------------------------------------------------------------------------------
1.0 Initial release
1.1 log will now be stored in a subfolder (name equals Identity)
1.2 PST file renaming added
1.3 Module ActiveDirectory removed. We use Get-Recipient now.
1.4 AcceptLargeDataloss would now be added if BadItemLimit is over 51
1.5 Parameter IncludeFodlers added
1.6 Parameter TargetFolder added
1.7 Parameter Recurse added
1.8 PST file rename after successful import added
1.9 Updated parameter set and some PowerShell hygiene
2.0 Updated documentation
.PARAMETER Identity
Type: string. Mailbox identity in which the pst files get imported
.PARAMETER Archive
Type: switch. Import pst files into the online archive.
.PARAMETER FilePath
Type:string. Folder which contains the pst files. Have to be a UNC path.
.PARAMETER FilenameAsTargetFolder
Type: switch. Import the pst files into targetfolders. The file name equals the target folder name.
.PARAMETER BadItemLimit
Type: int32. Standard is set to 0.
.PARAMETER ContinueOnError
Type: switch. If set the script continue with the next pst file if a import request failed.
.PARAMETER SecondsToWait
Type: int32. Timespan to wait between import request staus checks in seconds. Default: 320
.PARAMETER IncludeFolders
Type: string. If set the import would only import the given folder + subfolders. Note: If you want to import subfolders you have to use /* at the end of the folder. (Test/*).
.PARAMETER TargetFolder
Import the files in to definied target folder. Can't be used together with FilenameAsTargetFolder
.PARAMETER Recurse
If this parameter is set all PST files in subfolders will be also imported
.PARAMETER RenameFileAfterImport
Rename successfully imported PST files to simplify a re-run of the script. A .PST file will be renamed to .imported
.EXAMPLE
Import all PST file into the mailbox "testuser"
.\Start-MailboxImport.ps1 -Idenity testuser -Filepath "\\testserver\share"
.EXAMPLE
Import all PST file into the mailbox "testuser". Use PST filename as target folder name. Wait 90 seconds between each status check
.\Start-MailboxImport.ps1 -Idenity testuser -Filepath "\\testserver\share\*" -FilenameAsTargetFolder -SecondsToWait 90
#>
Param(
[parameter(Mandatory=$true)]
[string]$Identity,
[parameter()]
[switch]$Archive,
[parameter(Mandatory=$true)]
[string]$FilePath,
[parameter()]
[switch]$FilenameAsTargetFolder,
[parameter()]
[int]$BadItemLimit = 0,
[parameter()]
[switch]$ContinueOnError,
[parameter()]
[int]$SecondsToWait = 320,
[parameter()]
[string]$IncludeFolders="",
[parameter()]
[string]$TargetFolder="",
[parameter()]
[switch]$Recurse,
[parameter()]
[switch]$RenameFileAfterImport
)
# IMPORT GLOBAL MODULE
# Import GlobalFunctions
if($null -ne (Get-Module -Name GlobalFunctions -ListAvailable).Version) {
Import-Module -Name GlobalFunctions
}
else {
Write-Warning -Message 'Unable to load GlobalFunctions PowerShell module.'
Write-Warning -Message 'Open an administrative PowerShell session and run Import-Module GlobalFunctions'
Write-Warning -Message 'Please check http://bit.ly/GlobalFunctions for further instructions'
exit
}
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
$ScriptName = $MyInvocation.MyCommand.Name
# Create a log folder for each identity
$logger = New-Logger -ScriptRoot $ScriptDir -ScriptName $ScriptName -LogFileRetention 14 -LogFolder $Identity
$logger.Write('Script started')
$InfoScriptFinished = 'Script finished.'
<#
Helpder function to remove invalid chars for New-mailboxImportRequest cmdlet
#>
Function Optimize-PstFileName {
param (
[Parameter(Mandatory=$true)][string]$PstFilePath
)
if ($Recurse) {
# Find all files ending with .pst recursively in all folders
$Files = Get-ChildItem -Path $PstFilePath -Include '*.pst' -Recurse
}
else {
# Find all files ending with .pst in current folder
$Files = Get-ChildItem -Path $PstFilePath -Include '*.pst'
}
foreach ($pst in $Files) {
$newFileName = $pst.Name
# List of chars, add additional chars as needed
$chars = @(' ','(',')','&','$')
$chars | ForEach-Object {$newFileName = $newFileName.replace($_,'')}
$logger.Write("Renaming PST: Old: $($pst.Name) New: $($newFileName)")
if($newFileName -ne $pst.Name) {
# Rename PST file
$pst | Rename-Item -NewName $newFileName
}
}
}
# Check if -FilenameAsTargetFolder and -TargetFolder are set both
if (($FilenameAsTargetFolder) -and ($TargetFolder)) {
Write-Host '-FilenameAsTargetFolder and -TargetFolder can not be used together'
Exit(1)
}
# Get all pst files from file share
if ($FilePath.StartsWith('\\')) {
try {
# Check file path and add wildcard, if required
If ((!$FilePath.EndsWith('*')) -and (!$FilePath.EndsWith('\'))) {
$FilePath = $FilePath + '\*'
}
Optimize-PstFileName -PstFilePath $FilePath
# Fetch all pst files in source folder
if ($Recurse) {
$PstFiles = Get-ChildItem -Path $FilePath -Include '*.pst' -Recurse
}
else{
$PstFiles = Get-ChildItem -Path $FilePath -Include '*.pst'
}
# Check if there are any files to import
If (($PstFiles| Measure-Object).Count) {
$InfoMessage = "Note: Script will wait $($SecondsToWait)s between each status check!"
Write-Host $InfoMessage
$logger.Write($InfoMessage)
# Fetch AD user object from Active Directory
try {
$Name = Get-Recipient $Identity
}
catch {
$InfoMessage = "Error getting recipient $($Identity). Script aborted."
Write-Error $InfoMessage
$logger.Write($InfoMessage, 1)
}
foreach ($PSTFile in $PSTFiles) {
If ($Recurse) {
$ImportName = $($Name.SamAccountName + '-' + $PstFiles.DirectoryName + '-' + $PstFile.Name)
}
else {
$ImportName = $($Name.SamAccountName + '-' + $PstFile.Name)
}
$InfoMessage = "Create New-MailboxImportRequest for user: $($Name.Name) and file: $($PSTFile)"
# Built command string
# Checking BadItemLimit and add AcceptLargeDataLoss, if required
if ($BadItemLimit -gt 51) {
$cmd = "New-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) -FilePath ""$($PSTFile)"" -BadItemLimit $($BadItemLimit) -AcceptLargeDataLoss -WarningAction SilentlyContinue"
}
else {
$cmd = "New-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) -FilePath ""$($PSTFile)"" -BadItemLimit $($BadItemLimit) -WarningAction SilentlyContinue"
}
# Checking if -Archive is set
if ($Archive) {
$cmd = $cmd + ' -IsArchive'
$InfoMessage = "$($InfoMessage) into the archive."
}
else {
$InfoMessage = $InfoMessage + '.'
}
# Check TargetFolder setup
if ($FilenameAsTargetFolder) {
[string]$FolderName = $($PSTFile.Name.ToString()).Replace('.pst', '')
$cmd = $cmd + " -TargetRootFolder ""$($FolderName)"""
$InfoMessage = $InfoMessage + " Targetfolder:""$($FolderName)""."
}
if ($TargetFolder) {
$cmd = $cmd + " -TargetRootFolder ""$($TargetFolder)"""
$InfoMessage = $InfoMessage + " Targetfolder:""$($TargetFolder)""."
}
# Check if IncludeFolders is set
if ($IncludeFolders) {
$cmd = $cmd + " -IncludeFolders ""$($IncludeFolders)"""
$InfoMessage = $InfoMessage + " IncludeFolders:""$($IncludeFolders)""."
}
Write-Host $InfoMessage
$logger.Write($InfoMessage)
# Invoke command
try {
$null = Invoke-Expression -Command $cmd
}
catch {
$ErrorMessage = "Error accessing creating import request for user $($Name.Name). Script aborted."
Write-Error $ErrorMessage
$logger.Write($ErrorMessage,1)
Exit(1)
}
# Some nice sleep .zzzzzzzzzzz
Start-Sleep -Seconds 5
[bool]$NotFinished = $true
$logger.Write("Waiting for import request $($ImportName) to be completed.")
# Loop to check ongoing status of the request
while($NotFinished) {
try {
$ImportRequest = Get-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) -ErrorAction SilentlyContinue
switch ($ImportRequest.Status) {
'Completed' {
# Remove the ImportRequest so we can't run into the limit
$InfoMessage = "Import request $($ImportName) completed successfully."
Write-Host $InfoMessage
$logger.Write("$($InfoMessage) Import Request Statistics Report:")
# Fetch Import statistics
$importRequestStatisticsReport = (Get-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) | Get-MailboxImportRequestStatistics -IncludeReport).Report
# Write statistics to log, before we deleted the request (just in case need to lookup something)
$logger.Write($importRequestStatisticsReport)
# Rename imported PST-File if import was successful
if ($RenameFileAfterImport) {
$OldFilename = Get-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) | select-object -ExpandProperty FilePath
Rename-Item -Path "$($OldFilename)" -NewName "$($OldFilename).imported"
}
# Delete mailbox import request
Get-MailboxImportRequest -Mailbox $($($Name).SamAccountName) -Name $($ImportName) | Remove-MailboxImportRequest -Confirm:$false
$InfoMessage = "Import request $($ImportName) deleted."
Write-Host $InfoMessage
$logger.Write($InfoMessage)
$NotFinished = $false
}
'Failed' {
# oops, something happend
$InfoMessage = "Error: Administrative action is needed. ImportRequest $($ImportName) failed."
Write-Error $InfoMessage
$logger.Write($InfoMessage,1)
if (-not $ContinueOnError) {
Write-Host $InfoScriptFinished
$logger.Write($InfoScriptFinished)
Exit(2)
}
else {
$InfoMessage = 'Info: ContinueonError is set. Continue with next PST file.'
Write-Host $InfoMessage
$logger.Write($InfoMessage)
$NotFinished = $false
}
}
'FailedOther' {
# oops, something special happend and we need to take care about it.
Write-Error "Error: Administrative action is needed. ImportRequest $($ImportName) failed."
$logger.Write("Error: Administrative action is needed. ImportRequest $($ImportName) failed.",1)
if (-not $ContinueOnError) {
Write-Host $InfoScriptFinished
$logger.Write($InfoScriptFinished)
Exit(2)
}
else {
$InfoMessage = 'Info: ContinueonError is set. Continue with next pst file.'
Write-Host $InfoMessage
$logger.Write($InfoMessage)
$NotFinished = $false
}
}
default {
# default action: wait
Write-Host "Waiting for import $($ImportName) to be completed. Status: $($ImportRequest.Status)"
Start-Sleep -Seconds $SecondsToWait
}
}
}
catch {
$InfoMessage = "Error on getting Mailboximport statistics. Trying again in $($SecondsToWait) seconds."
Write-Host $InfoMessage
$logger.Write($InfoMessage, 1)
# wait before we try for the next step
Start-Sleep -Seconds $SecondsToWait
}
}
}
}
else {
$InfoMessage = "No files for import found in $($FilePath)."
Write-Host $InfoMessage
$logger.Write($InfoMessage)
}
}
catch {
$InfoMessage = "Error accessing $($FilePath). Script aborted."
Write-Error $InfoMessage
$logger.Write($InfoMessage, 1)
}
}
else {
$InfoMessage = 'Filepath have to be an UNC path. Scipt aborted.'
Write-Error $InfoMessage
$logger.Write($InfoMessage, 1)
}
# Done
Write-Host $InfoScriptFinished
$logger.Write($InfoScriptFinished)