Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Help with PST move script

Status
Not open for further replies.

M4rkC

MIS
Oct 26, 2009
13
GB
Hi Everybody,

I've cobbled together this script to move users local PST files to a network drive and then re-add them in Outlook.

The main part of the script comes from written by Andrew Healey & Nate Stevenson. There's also some manipulated extracts from examples.

I've re-worked the bits I need to so that it does what I want, putting the files in the right location and outputting to a log file instead of on-screen messages and all works ok.

The only problem I have is that it will only do this to the first PST it comes across. If there's two, it does the first and then finishes without errors.

I'm very new to VBS (as in today is the first time I've looked at it), but have looked through and can't logically see a reason for it not doing it. I'm sure I'm missing something simple.

Any pointers would be very appreciated!

Here's the code (please excuse the rem'd out bits there while I am working on it):

Code:
'==========================================================================
' VBScript Source File
' LAST EDITED: 26/10/09 by Mark Chamberlain, ICT Services
' NAME: move-pst-to-network
' COMMENT: This script will move PST files mapped in Outlook to the users
'	U:\ drive in a folder called OutlookPST
' CHANGES: 
' 
'==========================================================================

Option Explicit

'Determine if a laptop (remove if you don't care)
'If IsLaptop() = True Then
'	wscript.echo "Computer is a laptop or the chassis could not be determined."
'	wscript.echo "Exiting."
'	wscript.quit
'End If

'Create a logfile at C:\PSTlog.txt and opens it for writing
Dim fso, LogFile
Set fso = CreateObject("Scripting.FileSystemObject")
Set LogFile = fso.CreateTextFile("c:\PSTlog.txt", TRUE)

'Determine if outlook is installed
'If IsOutlookInstalled() = False Then
'	wscript.echo "Could not launch Outlook, script stopping."
'	LogFile.WriteLine ("Error: Could not launch Outlook, exiting.")
'	wscript.quit
'	End If

'Get user name
Dim WshNetwork : Set WshNetwork = WScript.CreateObject("WScript.Network")
Dim user : user = lcase(WshNetwork.UserName)
Set WshNetwork = Nothing

Dim strNetworkPath
'=========================================================================
' Configuration Section
strNetworkPath = "U:\OutlookPST\"
' End Configuration Section
'=========================================================================

'Fix network path if forgot to include trailing slash...
If Not Right(strNetworkPath,1) = "\" Then strNetworkPath = strNetworkPath & "\"

'Create folder for PST's on U:\ drive
'
'Checks if U:\OutlookPST exists, if not creates it
'and writes to log file with outcome

' Set variables for folder creation
Dim objFSO2, objFolder2, objShell2, strDirectory2
strDirectory2 = "u:\OutlookPST"

' Create the File System Object
Set objFSO2 = CreateObject("Scripting.FileSystemObject")

' Checks if folder exists, reports back
If objFSO2.FolderExists(strDirectory2) Then
   Set objFolder2 = objFSO2.GetFolder(strDirectory2)
'   WScript.Echo "OutlookPST folder already created"
	Logfile.Writeline("The OutlookPST folder already exists")
Else
   Set objFolder2 = objFSO2.CreateFolder(strDirectory2)
'   WScript.Echo "Created OutlookPST folder "
	Logfile.Writeline("Created the OutlookPST folder")
End If

'Determine if network path is writable
If IsPathWritable(strNetworkPath) = False Then
'	wscript.echo "Remote path is not writable, script stopping."
	LogFile.WriteLine ("Error: Cannot write to U:\OutlookPST\")
	wscript.quit
End If

'Instatiate objects
Dim objOutlook, objNS, objFSO, objFolder
Set objOutlook = CreateObject("Outlook.Application")
Set objNS = objOutlook.GetNamespace("MAPI")
Set objFSO = CreateObject("Scripting.FileSystemObject")

'Sort through all stores in outlook and add all local pst
' paths into an array. Then remove the store from outlook.
Dim pstFiles
Dim count : count = -1
Dim arrPaths()
For Each objFolder In objNS.Folders
	If GetPSTPath(objFolder.StoreID) <> "" Then
		pstFiles = GetPSTPath(objFolder.StoreID)
		If IsStoredLocal(pstFiles) = True Then
			If objFSO.FileExists(strNetworkPath & Mid(pstFiles,InStrRev(pstFiles,"\") + 1)) = True Then
				'wscript.echo "A pst file already exists with the same name." & vbCrLf & _
				LogFile.WriteLine("Error: A pst file already exists with the same name") & vbCrLf & _
						vbTab & "Source: " & pstPath & vbCrLf & _
						vbTab & "Target: " & strNetworkPath & Mid(pstPath,InStrRev(pstPath,"\") + 1)
			Else
				count = count + 1
				ReDim Preserve arrPaths(count)
				arrPaths(count) = pstFiles
				objOutlook.Session.RemoveStore objFolder
			End If
		End If
	End If
Next

objOutlook.Session.Logoff
objOutlook.Quit
Set objOutlook = Nothing
Set objNS = Nothing

' Writes PST path in log for each entry in array, if any
'If count >= 0 Then
'	For Each present In arrPaths
'	LogFile.WriteLine(present)
'	LogFile.WriteLine("<br/>")

' If no entries found in array, write to log and quit
if count < 0 then
'	wscript.echo "No local PST Files Found."
	LogFile.WriteLine("No local PST files found to move.")
	wscript.quit
End If

'If local PST files were found, move them to the new location
'Echo output if the file already exists
Dim pstPath
For Each pstPath in arrPaths
	On Error Resume Next
		objFSO.MoveFile pstPath, strNetworkPath
		If Err.Number <> 0 Then
			wscript.sleep 5000
			objFSO.MoveFile pstPath, strNetworkPath
		End If
	Err.Clear
	On Error GoTo 0
Next
Set objFSO = Nothing

'Re-open outlook
Set objOutlook = CreateObject("Outlook.Application")
Set objNS = objOutlook.GetNamespace("MAPI")

'Re-map Outlook folders
For Each pstPath in arrPaths
	objNS.AddStore strNetworkPath & Mid(pstPath,InStrRev(pstPath,"\") + 1)
	LogFile.Writeline("PST re-mapped in Outlook")
Next

objOutlook.Session.Logoff
objOutlook.Quit
Set objOutlook = Nothing
Set objNS = Nothing
'wscript.echo "Done."
LogFile.WriteLine("Script Finished")
LogFile.WriteLine(Date)
LogFile.WriteLine(Time)
wscript.quit

'Closes the open log file, no more to write
LogFile.Close

Private Function GetPSTPath(byVal input)
	'Will return the path of all PST files
	' Took Function from: [URL unfurl="true"]http://www.vistax64.com/vb-script/[/URL]
	Dim i, strSubString, strPath
	For i = 1 To Len(input) Step 2
		strSubString = Mid(input,i,2)
		If Not strSubString = "00" Then
			strPath = strPath & ChrW("&H" & strSubString)
		End If
	Next

	Select Case True
		Case InStr(strPath,":\") > 0
			GetPSTPath = Mid(strPath,InStr(strPath,":\")-1)
		Case InStr(strPath,"\\") > 0
			GetPSTPath = Mid(strPath,InStr(strPath,"\\"))
	End Select
End Function

'Private Function IsLaptop()
'	'Determine if the computer is a mobile machine
'	On Error Resume Next
'		'Instantiate objects
'		Dim objWMIService, colChassis, objChassis, strChassisType
'		Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
'		Set colChassis = objWMIService.ExecQuery("Select * from Win32_SystemEnclosure")
'		
'		'Check chassis type
'		'[URL unfurl="true"]http://msdn.microsoft.com/en-us/library/aa394474%28VS.85%29.aspx[/URL]
'		For Each objChassis in colChassis
'			For  Each strChassisType in objChassis.ChassisTypes
'				If (strChassisType >= 8 And strChassisType <=12) Or (strChassisType = 14) Then
'					IsLaptop = True
'					Exit For
'				Else
'					IsLaptop = False
'				End If
'			Next
'		Next
'	If Err.Number <> 0 Then IsLaptop = False
'	On Error GoTo 0
'	Set colChassis = Nothing
'	Set objWMIService = Nothing
'	objChassis = Null
'End Function 

'Private Function IsOutlookInstalled()
	'Function will return false if unable to launch outlook
	' This adds some overhead but it is ultimately the best
	' way to truly determine if script will function properly.
'	On Error Resume Next
'		Set objOutlook = CreateObject("Outlook.Application")
'		If Err.Number <> 0 Then
'			IsOutlookInstalled = False
'			Exit Function
'		End If
'	On Error GoTo 0
'	IsOutlookInstalled = True
'	objOutlook.Session.Logoff
'	objOutlook.Quit
'	Set objOutlook = Nothing
'End Function

Private Function IsPathWritable(byVal strPath)
	'Check to make sure the path is writable. If it is not, no
	' need to continue processing.
	On Error Resume Next
		Set objFSO = CreateObject("Scripting.FileSystemObject")
		Dim min : min = 1
		Dim max : max = 1000
		Dim rand : rand = Int((max - min + 1) * Rnd + min)
		Dim fullFileName : fullFileName = strPath & "temporary-" & rand & ".txt"
		Dim objFile : Set objFile = objFSO.CreateTextFile(fullFileName, True)
		objFile.WriteLine("Test file creation of " & fullFileName)
		objFile.Close
		If objFSO.FileExists(fullFileName) Then
			IsPathWritable = True
			objFSO.DeleteFile(fullFileName)
		Else
			IsPathWritable = False
		End If
	If Err.Number <> 0 Then IsPathWritable = False
	On Error GoTo 0
	Set objFile = Nothing
	Set objFSO = Nothing
	rand = Null
	max = Null
	min = Null
	fullFileName = Null
End Function

Private Function IsStoredLocal(ByVal fullFileName)
	'Check if the PST is stored locally or on a mapped or removable drive
	On Error Resume Next
		Dim objDisk, objWMIService, colDisks
		Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
		Set colDisks = objWMIService.ExecQuery("SELECT * FROM Win32_LogicalDisk")
		For Each objDisk in colDisks
			If objDisk.DriveType = 3 Then
				If InStr(fullFileName,objDisk.DeviceID) > 0 Then
					IsStoredLocal = True
					Exit For
				Else
					IsStoredLocal = False
				End If
			End If
		Next
	If Err.Number <> 0 Then IsLocalDrive = False
	On Error GoTo 0
End Function

Many thanks,

Mark.
 
It sounds like arrPaths() only contains one element and thus only copies one file. Comment out the "On Error Resume Next" and throw a msgbox in there to view the contents of the array as it loops.

Code:
'If local PST files were found, move them to the new location
'Echo output if the file already exists
Dim pstPath
For Each pstPath in arrPaths
    [s]On Error Resume Next[/s]
    [red]msgbox pstPath[/red]
    objFSO.MoveFile pstPath, strNetworkPath
    If Err.Number <> 0 Then
       wscript.sleep 5000
       objFSO.MoveFile pstPath, strNetworkPath
    End If
    Err.Clear
    On Error GoTo 0
Next

With this, you should be able to deduce the culprit.

-Geates
 
Hi Geates,

Thanks very much, I'll give that a go tomorrow when I'm back at work and report back on the outcome.

Mark.
 
This might not sound helpful, but

>PST files to a network drive and then re-add them in Outlook

do not put PSTs on a network drive. They are unsupported in that configuration, and the conflict between the way network read/writes occur and the way Outlook wants to read/write the file will lead to poor performance and will corrupt the PST at some point. It may even lead to the server the PST(s) are stored on to lock up (although I've not encountered that particular issue). For a more in depth comment on this:
 
strongm, it is indeed helpful. I won't shout at you just because it's not what I want to hear!

I'll have a read of that link, thanks.

Our problem is an XP upgrade program (yes we are only just getting to finish upgrading our 5000 computers to XP..) has accidentally deleted some local PST files that were not backed up. They have group policy in place to stop users storing the files locally but this hasn't worked everywhere. It seems maybe we shouldn't even do this!

Sometimes, senior management demand things that IT wouldn't like to do, but have to do to keep toys in prams..
 
I know exactly what you are dealing with. We suffer from the same user incompontents. We officially do not support archiving. However, this is unexceptable in the eyes of few, at least for them (upper management). We put user archive (for those that choose to archive) up on their u: drive one the condition that they understand the archive will NOT always be available.

-Geates
 
If you have to allow your users to use PST files you might be better off to write a script that stops Outlook if it is running then copies the file to a network share for pickup by tape or whatever. You avoid the problems of network PSTs, yet still get backups. You may only get daily snapshots and still have that much loss window, but it's better than nothing.



Jeff
[small][purple]It's never too early to begin preparing for [/purple]International Talk Like a Pirate Day
"The software I buy sucks, The software I write sucks. It's time to give up and have a beer..." - Me[/small]
 
We run exchange and have a network archive solution, yet people still like their own PST's sometimes. We reckon about 1500/5000 still have an archive PST stored locally.

I like the file copy idea, but I think it will be hard to implement. We have a large population of laptop hot-deskers, and an even larger population of impatient users that will not wait patiently whilst they're oversize PST's copy across the network.

I then have the task of working out how we're going to attempt any of this at some of our remote sites which can be running at around 2Mb/s or slower broadband (that's 256K upload).

It's a good job I love my job because of the challenge ;o)
 
Well, basically lay out their options and it's their choice.

1. Run totally local and risk archive loss from lack of backups.
2. Run over network and rick archive loss from instability leading to corruption
3. Put up with hassle of daily backup window.
4. Give up on PST archives.

I think that's it.

Jeff
[small][purple]It's never too early to begin preparing for [/purple]International Talk Like a Pirate Day
"The software I buy sucks, The software I write sucks. It's time to give up and have a beer..." - Me[/small]
 
They have decided that the risk of loss from network corruption is far less than from hard disk failure. They figure that they will have copies on backup tapes should corruption occur.

So, they still want me to get this working.

I've tried the above suggestion of reading out of the array, it only contains one PST, so there must be an error with it reading in to the array.

Has anybody got any more suggestions please?

Thanks!

Mark.
 
Oh well, it's their decision. Remember not to say "I told you so" when things start to go wrong ... ;-)

 
I found a site that may accomplish what you are looking to do.

Keep in mind that once the archive is copied to the network share, Outlook needs to be configured to reflect the new location. Also, if the location is not available, Outlook will create a local PST and continue to use it until told otherwise.

It's an issue that has plagued the supporting of Outlook archives. A user may take thier laptop home and open outlook. This laptop will need to be reconfigured to look at the network archive - not the local.

Personally, I think it boils down to education. There needs to be a culture change amongst your users that will make archiving adminitratively feasible and user friendly.

-Geates
 
However, I have not seen this behavior (local PST when network PST absent) in 2007.

-Geates
 
Thanks Geates.

The script I have above does exactly what I want, and works perfectly apart from the fact it will only do one at a time.

It finds only local PST's, removes them from the Outlook profile and then copies them to the specified network location. Then it re-adds them to the Outlook profile, with the user blissfully unaware that anything has changed.

I just need to work out why it is only reading one PST location at a time in to the array.

Can anybody offer any advice on it?
 
I think the key is in the first for each loop

Code:
[red]For Each objFolder In objNS.Folders[/red]
    If GetPSTPath(objFolder.StoreID) <> "" Then
        pstFiles = GetPSTPath(objFolder.StoreID)
        If IsStoredLocal(pstFiles) = True Then
            [green]If objFSO.FileExists(strNetworkPath & Mid(pstFiles,InStrRev(pstFiles,"\") + 1)) = True Then[/green]
                'wscript.echo "A pst file already exists with the same name." & vbCrLf & _
                LogFile.WriteLine("Error: A pst file already exists with the same name") & vbCrLf & _
                        vbTab & "Source: " & pstPath & vbCrLf & _
                        vbTab & "Target: " & strNetworkPath & Mid(pstPath,InStrRev(pstPath,"\") + 1)
            Else
                [blue]count = count + 1
                ReDim Preserve arrPaths(count)
                arrPaths(count) = pstFiles[/blue]
                objOutlook.Session.RemoveStore objFolder
            End If
        End If
    End If
Next

[red]The loop runs for each element of objNS.Folders[/red]
[green]The folder is merely checked to see if a PST exists.[/green]
[blue]If it doesn't, the path is added to arrPaths [/blue]and the next folder is checked. No where in the loop does it check for multiple PSTs. If a folder contains a non-zero number of PST, the path to that folder is added - not paths.

The code need to be modified to search the directory for ALL PST files, not just any.

.
.
.

Well, I wanted to make sure what I am saying is actually true...It sort of is.

Note: My finding were with Outlook 2007

I used a msgbox of pstFiles and found that the value was c:\path\Outlook.pst. Hmm, my archive file is archive.pst. I set up 3 more archives all named uniquely but pstFiles still had a value of Outlook.pst. Conclusion? Regardless of the number of PSTs, the code uses stagnant data from the Outlook NameSpace object instead of populating the PST paths.

Although, I may have completely missed the mark.

-Geates
 
Code:
For Each objFolder In objNS.Folders

traverses the folders of the Outlook namespace (NS):

All Mail Items
- Public Folder (NS1)
- Mailbox - <Full User Name> (NS2)
- Archive Folders (NS3)

Each NS folder contains a StoreID that reflect ONE PST file, even if there are more PST in the path. Generally, the only NS that contains PST files is "Archive Folder".

In order to move ALL PSTs you'll have to explicitly look at the users outlook folder and move the files to the network share. Because the objNS.AddStore method accepts a string (not an object) as it's parameter, you can add ALL PSTs to the outlook profile. However, Outlook will organize them so there is one PST per store.

Assuming you have 3 PSTs you want to add, the Outlook NameSpace will look like this:

All Mail Items
- Public Folder (NS1)
- Mailbox - <Full User Name> (NS2)
- Archive Folders (NS3)
- Archive Folders (NS4)
- Archive Folders (NS5)

I'll have a script up in a short time but for now I must go play disc golf.

- Geates

PS. I am very interested in this issue because it is similar to those that I deal with. Even though I may be wrong at time, this thread is has brought a concept to my attention that I never considered before. I thank everyone that has contributed to this thread - they are invaluable.
 
Hi Geates, thanks for the extra info.

I've spent today scratching my head over the script and have managed to get it working, but in my own very crude and 'I-don't-totally-understand-vbs' way.

In essence, because it would only work for the first store it read each team, I have installed a Do Until loop. It took some fiddling to get the right bits in the right place, but it works for me.

I've left in place the array although I could do this without, but I didn't want to have to re-write the entire thing when there is stuff in there I don't understand.

I'll happily post up the resultant code, but please don't flame me for it being awful. As I said, it's crude but it works!
 
I scratched my head too! After a while, I realised how to do it.

1. Get all Outlook stores
2. Get all PSTs contained in the stores path
3. Exclude PSTs from non-archive stores
4. Move PSTs
5. Add moved PSTs as new stores

Code:
'************************************************************************
'DESCRIPTION:   Move only archive PSTs to network and reconfigure outlook
'WRITTEN BY:    Daniel M. Jones
'DATE:		October 28, 2009
'COMMENT:	Adopted from move-pst-to-networks by Mark Chamberlain
'************************************************************************

'************************************************************************
' VARIABLE DEFINITION
'************************************************************************

set objFSO = CreateObject("Scripting.FileSystemObject")
set objNetwork = WScript.CreateObject("WScript.Network")

strUser = lcase(objNetwork.UserName)
strNetworkPath = "c:\temp\archive\" '"\\server\" & strUser & "\OutlookPST\"

'************************************************************************
' FUNCTIONS
'************************************************************************

function getStore(strHexID)
	for i = 1 to len(strHexID) step 2
		strSubString = mid(strHexID, i, 2)
        if (strSubString <> "00") then
            strPath = strPath & chrw("&H" & strSubString)
        end if
    next

    if (inStr(strPath, ":\")) then getStore = mid(strPath, inStr(strPath,":\") - 1)
    if (inStr(strPath, "\\")) then getStore = mid(strPath, inStr(strPath,"\\") - 1)
end function

'************************************************************************
' BEGIN
'************************************************************************

set dicPaths = CreateObject("Scripting.Dictionary")
set dicExcludedPaths = CreateObject("Scripting.Dictionary")
set objOutlook = CreateObject("Outlook.Application")
set objNS = objOutlook.GetNamespace("MAPI")

'Get all PSTs from all Outlook stores.
for each objStore in objNS.Folders
	strStore = getStore(objStore.StoreID)
	strStorePath = left(strStore, inStrRev(strStore, "\"))
	strStoreFile = mid(strStore, inStrRev(strStore, "\") + 1)
	if (objStore.Name = "Archive Folders") then
		set objFiles = objFSO.GetFolder(strStorePath).Files
		for each objFile in objFiles
			msgbox objFile.Name
			if (lcase(right(objFile.Name, 4) = ".pst")) then
				dicPaths.Add objFile.Name, objFile.Path
				if (objFile.Name = strStoreFile) then objOutlook.Session.RemoveStore objStore
			end if
		next
	else
		dicExcludedPaths.Add strStoreFile, strPath
	end if
next

'Exclude non-archive PST paths
for each strKey in dicExcludedPaths.Keys
	if (dicPaths.Exists(strKey)) then
		dicPaths.Remove(strKey)
	end if
next


'Copy PST to network and add as new store
for each strPath in dicPaths.Items
	objFSO.MoveFile strPath, strNetworkPath
	strFileName = mid(strPath, inStrRev(strPath, "\") + 1)
	objNS.AddStore strNetworkPath & strFileName
next	

objOutlook.Session.Logoff
objOutlook.Quit

While most uses will be with general condition of one archive store and PST, this code accounts for those PSTs that not belong to a store and those stores that may not have a PST.

- Geates
 
To prevent file access errors, the outlook system process needs to be killed to release it's grip on the PSTs.

Added the following between step 2 and 3.

Code:
objOutlook.Session.Logoff
objOutlook.Quit
set colProcesses = objWMI.ExecQuery("Select * from Win32_Process where Name='OUTLOOK.EXE'")
for each objProcess in colProcesses
	objProcess.Terminate()
next
wscript.sleep 2000

final code can be found at
-Geates
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top