Seks
(Fresh Scripter)
2009-05-11 11:47 PM
Searching PSTs on PC's

When a user logs in, I want a search done and an export of a list of PST's on the users' PC, including the paths, dates and file sizes.

Is it possible with Kix?


Mart
(KiX Supporter)
2009-05-12 09:18 AM
Re: Searching PSTs on PC's

Sure it’s possible but crawling all files and folders and filtering out .pst files might take a some time so doing this during logon can cause massive delays. Running it as an admin script or scheduled to run x minutes after logon might be better.

The code below is NOT tested so be careful when running it.
It should get you started into the right direction I think.

 Code:
Break on

;Enumerate all .pst files and the C drive.
$pstfiles = dirplus("c:\", "/s /a-d /m .pst")

;Do some magic when more then 0 .pst files are found.
If UBound($pstfiles) > -1
	;Open text file for logging the .pst files.
	$rc = Open(1, "\\server\share\" + @WKSTA + ".txt", 5)
	;Split the .pst files array and do the magic on each single file.
	For Each $pstfile in $pstfiles
		;Write file info to the log file.
		$rc = WriteLine(1, $pstfile.name + " - " + $pstfile.Datecreated + " - " + $pstfile.size + " - " + $pstfile.path + @CRLF)
	Next
	;Close the logfile.
	$rc = Close(1)
EndIf



;=-=-=-=-=-=-=- DO NOT MODIFY ANYTHING BELOW THIS LINE -=-=-=-=-=-=-=
;=-=-=-=-=-=-=- THIS IS A UDF AND IT IS READY FOR USE AS IT IS -=-=-=-=-=-=-=

;Function		DIRPlus()
;
;Author		Bryce Lindsay bryce@isorg.net
;
;Action		Returns an array containing directory files and folders
;
;Syntax		DIRPLUS("PATH","OPTIONS")
;
;Version		2.34
;
;Date Revised	2-10-05
;			2005.09.20 2.34 Filed the file/folder option "d" to be non language dependent, thanks Jochen
;
;Parameters	Path
;		  Full path To To a folder that you want To Return information on.
;		  "c:\program files"
;
;		  OPTIONS
;		  /S          Displays files In specified directory and all subdirectories.
;                             Use a /S# where # is equal to the subfolder depth that you want to recurse.
;
;		  /A          Displays files with specified attributes.
;		  attributes   D  Directories                R  Read-only files
;		               H  Hidden files               A  Files ready For archiving
;		               S  System files               -  Prefix meaning not
;
;		  /M          Apply mask string To filter based on InSTR(), separate Each search string witha a |
;
;		  /F          Return a given File extension like exe log or txt Seperate each extension type with a space
;
;
;Remarks	Finaly fixed this UDF For To handle multiple recursions,
;		also should have a faster responce time since it is using the FSO
;
;		***Please note that the syntax For version 2.0 of this UDF has changed.***
;
;		made some tweeks using feedback from Les! thanks!  Also NTDOC!
;
;Returns	Returns and array of FSO objects that are equal the file and folder objects of
;		the given path.  Also returns a @ERROR code For event handling.
;
;Dependencies 	FSO
;
;KiXtart Ver	4.22
;
;Example(s)	$Dir = dirplus("c:\program files") ;returns all files and folders In the "c:\program files" folder
;		$Dir = dirplus("c:\program files","/s") ;all fiels and folders including subfolders
;		$Dir = dirplus("c:\","/a-d") ;returns only a list of files In the c:\
;		$Dir = dirplus("c:\","/ad") ;returns only a list of folders In the c:\
;		$Dir = dirplus("c:\program files","/ad /s") ;returns only the folders including all subfolders.
;
;		$Dir = dirplus("g:\kix\udf","/s /ad /m dir") ; recursive subfolder search, folders only, using a mask string of "dir"
;
;		$desktop = dirplus("%userprofile%\desktop","/a-d")
;		For Each $file In $desktop
;			? $file
;			? $file.size
;		Next
;
Function DirPlus($path, optional $Options, optional $f, optional $sfflag)
	If Not VarType($f) Dim $f EndIf
	If Not VarType($sfflag) Dim $sfflag EndIf
	
	Dim $file, $i, $temp, $item, $ex1, $mask, $mask1, $maskArray, $maskarray1,
	$ex2, $code, $CodeWeight, $targetWeight, $weight, $masktrue
	Dim $tarray[0]
	
	$ex1 = SetOption(Explicit, on)
	$ex2 = SetOption(NoVarsInStrings, on)
	$codeWeight = 0
	
	If Not Exist($path) 
		$temp = SetOption(Explicit, $ex1)
		$temp = SetOption(NoVarsInStrings, $ex2)
		Exit @ERROR
	EndIf
	
	If Not VarType($f)
		$f = CreateObject("Scripting.FileSystemObject").getfolder($path)
	EndIf
	If @ERROR 
		$temp = SetOption(Explicit, $ex1)
		$temp = SetOption(NoVarsInStrings, $ex2)
		Exit @ERROR
	EndIf
	
	For Each $temp in Split($options, "/")
		$temp = Trim($temp)
		Select
			Case Left($temp, 1) = "s"
				If Not VarType($sfflag)
					If Val(Right($temp, -1)) = 0
						$sfflag = -1
					Else
						$sfflag = Val(Right($temp, -1))
					EndIf	
				EndIf
			Case Left($temp, 1) = "a"
				Select
					Case Right($temp, -1) = "d"
						$codeWeight = $codeWeight + 1
						$temp = "if $file.attributes & 16 " ;"if $file.type = 'File Folder' "
					Case Right($temp, -1) = "-d"
						$codeWeight = $codeWeight + 1
						$temp = "if ($file.attributes & 16)=0 " ;"if $file.type <> 'File Folder' "
					Case Right($temp, -1) = "s"
						$codeWeight = $codeWeight + 1
						$temp = "if $file.attributes & 4 "
					Case Right($temp, -1) = "-s"
						$codeWeight = $codeWeight + 1
						$temp = "if ($file.attributes & 4)=0 "
					Case Right($temp, -1) = "h"
						$codeWeight = $codeWeight + 1
						$temp = "if $file.attributes & 2 "
					Case Right($temp, -1) = "-h"
						$codeWeight = $codeWeight + 1
						$temp = "if ($file.attributes & 2)=0 "
					Case Right($temp, -1) = "r"
						$codeWeight = $codeWeight + 1
						$temp = "if $file.attributes & 1 "
					Case Right($temp, -1) = "-r"
						$codeWeight = $codeWeight + 1
						$temp = "if ($file.attributes & 1)=0 "
					Case Right($temp, -1) = "a"
						$codeWeight = $codeWeight + 1
						$temp = "if $file.attributes & 32 "
					Case Right($temp, -1) = "-a"
						$codeWeight = $codeWeight + 1
						$temp = "if ($file.attributes & 32)=0 "
				EndSelect
				$code = $temp + "$weight=$weight+1 endif" + @CRLF + $code
				
			Case Left($temp, 1) = "m"
				$maskarray = Split(Right($temp, -2), "|")
				$codeweight = $codeweight + 1
				$code = "$masktrue=0 for Each $mask in $maskarray if instr($file.name,$mask) $masktrue=1 " +
				"EndIf Next If $masktrue $weight=$weight+1 endif" + @CRLF + $code
			Case Left($temp, 1) = "f"
				$maskarray1 = Split(Right($temp, -2), " ")
				$codeweight = $codeweight + 1
				$code = "$masktrue=0 for Each $mask1 in $maskarray1 if substr($file.name,Instrrev($file.name,'.')+1)" +
				"=$mask1 $masktrue=1 EndIf Next If $masktrue $weight=$weight+1 endif" + @CRLF + $code
				
		EndSelect
	Next
	$code = "$weight = 0 $targetWeight = " + $codeweight + @CRLF + $code
	$code = $code + "if $weight = $targetweight Exit 1 endif"
	
	For Each $file in $f.subfolders
		If Execute($code)
			$tarray[$i] = $file
			$i = $i + 1
			ReDim preserve $tarray[$i]
		EndIf
		If $sfflag
			$temp = dirplus($file, $options, $file, $sfflag - 1)
			For Each $item in $temp
				$tarray[$i] = $item
				$i = $i + 1
				ReDim preserve $tarray[$i]
			Next
		EndIf
	Next
	For Each $file in $f.files
		If Execute($code)
			$tarray[$i] = $file
			$i = $i + 1
			
			ReDim preserve $tarray[$i]
		EndIf
	Next
	
	If $i
		ReDim preserve $tarray[$i - 1]
		$i = 0
	Else
		$tarray = 0
	EndIf
	
	$dirplus = $tarray
	$temp = SetOption(Explicit, $ex1)
	$temp = SetOption(NoVarsInStrings, $ex2)
	Exit @ERROR
EndFunction


NTDOCAdministrator
(KiX Master)
2009-05-13 05:26 AM
Re: Searching PSTs on PC's

That's going to suck for people with LARGE hard drives and thousands of folders and files if it runs every time they logon.

BradV
(Seasoned Scripter)
2009-05-13 12:59 PM
Re: Searching PSTs on PC's

Right, and it isn't counting on the folks that keep their pst files on a network share. We don't do any backups of local PCs. So, everyone is told that if it is important, they better keep it on a network share. My pst's are in my home share.

Glenn BarnasAdministrator
(KiX Supporter)
2009-05-13 01:57 PM
Re: Searching PSTs on PC's

Something else to consider.. It seems that FSO bogs down in large structures.. I recently had to check a folder with 50K+ files in around 8K folders.. DirPlus was the slowest, DirList was a bit faster, and Shell to DIR was the fastest method to enumerate a large structure and get a list of files. Point is - test alternate methods!

Brad's right about locally stored data. We don't "permit" it either, although we have no controls to prevent it.

We do have one process that runs in the login script that can be time consuming - we write the date it ran to the local registry, so if the stored date is missing or more than 30 days old, we run the task. This way it only runs once every 30 days.

Glenn


Seks
(Fresh Scripter)
2009-05-14 04:09 PM
Re: Searching PSTs on PC's

 Originally Posted By: NTDOC
That's going to suck for people with LARGE hard drives and thousands of folders and files if it runs every time they logon.


Well I ain't that dumb :]

 Quote:


IF INGROUP("PST_SEARCH")
GOTO "PST_SEARCH"
ELSE
GOTO "END"
ENDIF

;---------------------------------------------------

:PST_SEARCH

IF EXIST("\\cgi-ahwd1\logs$\pst_by_username\@USERID---@WKSTA.txt")
GOTO "END"
ENDIF

Break on

;Enumerate all .pst files and the C drive.
.
.
.

:end



 Quote:
Shell to DIR was the fastest method to enumerate a large structure and get a list of files


Where do you find that? What is that?


Mart
(KiX Supporter)
2009-05-14 04:29 PM
Re: Searching PSTs on PC's

A small modification because GOTO's are bad, they are really evil and should be avoided like a 250 kilo person at a pie eating contest, you have some macro’s in strings and a $ that could be mistaken by kix for a variable.

 Code:
Break on

$rc = SetOption("NoVarsInStrings", "On")

If InGroup("PST_SEARCH")
	If Not Exist("\\cgi-ahwd1\logs$\pst_by_username\" + @USERID + "---" + @WKSTA + ".txt")
		;do your magic here
	EndIf
EndIf


Björn
(Korg Regular)
2009-05-14 04:58 PM
Re: Searching PSTs on PC's

I think I recall that the path to the pst's are in the registry, am I wrong here or is your find-quest just a tad out of place?

Mart
(KiX Supporter)
2009-05-14 09:21 PM
Re: Searching PSTs on PC's

I have two pst archive file linked and non of them show up in the registry (besides in MRU lists) so I do not think that that is an option.

PST’s are in a default location (C:\Documents and Settings\usernamegoeshere\Local Settings\Application Data\Microsoft\Outlook if I'm correct) but anyone can put one anywhere he/she likes.

Doing a search on .pst files is the most effective and maybe even the only way to get them all.


BradV
(Seasoned Scripter)
2009-05-15 11:57 AM
Re: Searching PSTs on PC's

Yea, but limiting the search to the c drive will likely miss many. I keep mine on my home drive so they are always available anywhere I go and they get backed up.

Glenn BarnasAdministrator
(KiX Supporter)
2009-05-15 12:55 PM
Re: Searching PSTs on PC's

They are in the registry.. I'll post something when I get to the office that will locate every PST file that the user has defined. I used it a few months ago to migrate user PST data to an email archive system.

Glenn


BradV
(Seasoned Scripter)
2009-05-15 01:02 PM
Re: Searching PSTs on PC's

Well, I found my list at:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU\pst

The value, MRUList, data lists the values of all pst's currently(?) loaded. So, read MRUList, and then each character is a value in the same section which points to the pst.

Thanks Glenn! Learn something new everyday. \:\)


Glenn BarnasAdministrator
(KiX Supporter)
2009-05-15 01:19 PM
Re: Searching PSTs on PC's

Nope - that's not an authoritative list, just what's been recently used. The actual location of every defined PST is stored in GUIDs and "encoded" to our eyes by being written in unicode.

I have to dash - I'll post the full code and info shortly.. there's enough data, code, and processes for a vault post!

Glenn


Richard H.Administrator
(KiX Supporter)
2009-05-15 01:27 PM
Re: Searching PSTs on PC's

MRU is not a good source for the information.

The PST files are stored in the messenging subsystem registry entries, but they are a little tricky to locate as they are binary objects.

Have a look at the profiles under HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles

You will need to decipher these but for example look for the subkey 0a0d020000000000c000000000000046 and then take a look in the binary entries. On my machine 001f0324 has my primary archive PST.


Glenn BarnasAdministrator
(KiX Supporter)
2009-05-15 02:56 PM
Re: Searching PSTs on PC's

This is the script I used to locate the user's defined PST files. This script will
  • Terminate outlook, if running
  • disassociate the PST files
  • rename the .PST file to .STP (hiding it)
  • write the file location to a log file, where another process can move, rename, and feed it to the archiving utility.

There is enough code here to easily extract the parts to identify the PST files that the user has defined. I'll post the complete suite of tools & docs in the vault.

Glenn


;; KixGenerated: 2008/03/24 16:50:52 
; outlook mail archiving migration tool 
; Glenn Barnas - 2008/03/24 
; This script is designed to run during the logon process and perform the following actions: 
;  * Terminate Outlook, if running 
;  * Locate all PST files associated with the current user 
;  * Disassociate PST files from outlook This can be done for all PSTs, or only those stored on network drives 
;  * rename the PST file so users/Outlook cannot readily reopen it 
;  * log the location of the PST file(s) in UNC format 
 
 
 
Break On
 
Dim $, $_					; temp, throwaway var 
Dim $aPSTPaths[0]				; Array of PST paths 
Dim $aPSTKeys[0]				; Array of PST key paths 
Dim $RootPath					; registry root path 
Dim $WKey					; working key path 
Dim $EKey					; enumerated registry path 
Dim $TVal					; temp value for 
Dim $aIndex, $eIndex				; index pointers for array and enumeration 
Dim $MServer, $DName, $DProfile			; user data vars 
Dim $aMapped					; array of mapped network drives 
Dim $Drv, $NetFlag, $IsNet			; Drive letter enumerator, Network Only flag, IsNetwork flag 
Dim $PSTName, $PSTPath				; name & Path of current PST file 
Dim $NewPSTName					; name of renamed PST file 
Dim $LogPath					; UNC path where logs are written 
 
 
$ = SetOption('Explicit', 'On')
$ = SetOption('WrapAtEOL', 'On')
$ = SetOption('NoVarsInStrings', 'On')
$ = SetOption('NoMacrosInStrings', 'On')
 
; # # # # # START OF CUSTOM PARAMETERS # # # # # 
$NetFlag = 1					; only process network PST files if 1 
$LogPath = '\\server\share\folder'		; UNC path where renamed PST file locations are logged 
$LogPath = '\\lgissappp01\Logs\PST'
; # # # # #  END OF CUSTOM PARAMETERS  # # # # # 
 
 
$RootPath = 'HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles'
 
; get list of mapped drives 
$aMapped = WMIMappedDrives()
 
$DProfile = ReadValue($RootPath, 'DefaultProfile')
$WKey = $RootPath + '\' + $DProfile + '\'
 
$MServer = ReadValue($WKey + '13dbb0c8aa05101a9bb000aa002fc45a', '001e6602')
$DName   = UtoA(ReadValue($WKey + '13dbb0c8aa05101a9bb000aa002fc45a', '001f3001'))
 
; find the PST files associated with this profile - need to enumerate each subkey and find the 001f6700 value 
$eIndex = 0
$aIndex = -1
$EKey = EnumKey($WKey, $eIndex)
While Not @ERROR
  $TVal = UtoA(ReadValue($WKey + $EKey, '001f6700'))
  If $TVal
    $aIndex = $aIndex + 1
    ReDim Preserve $aPSTPaths[$aIndex]
    ReDim Preserve $aPSTKeys[$aIndex]
    $aPSTPaths[$aIndex] = $TVal
    $aPSTKeys[$aIndex] = $WKey + $EKey
  EndIf
  $eIndex = $eIndex + 1
  $EKey = EnumKey($WKey, $eIndex)
Loop
 
; Exit if nothing to do 
If $aIndex < 0
  'No PST files are registered for user ' $DName ' - exiting!' ? ?
  Exit 0
EndIf
 
? ? 'Preparing for Mail Archiving on ' @WKSTA ' for user ' $DName ?
 
; now have all needed info 
;  - terminate Outlook if it is running 
;  - delete the PST registry keys 
;  - rename the PST files 
 
; terminate Outlook  
$ = Split(WMIProcessList('outlook.exe')[0], ',')[1]
If Not @ERROR
  ' Closing Outlook...' ?
  $ = WMIProcessKill($)				; terminate Outlook 
  Sleep 3					; wait for Outlook to close open files 
EndIf
 
 
; Enumerate the PST files that are registered in Outlook 
For $ = 0 to UBound($aPSTPaths)
 
  $IsNet = -1					; Default to Not Network, will be 0 or more if network PSTs are found 
 
  $Drv = Left($aPSTPaths[$], 2)			; drive letter where PST is stored 
  $_ = InStrRev($aPSTPaths[$], '\')
  $PSTName = SubStr($aPSTPaths[$], $_ + 1)	; get name part 
  $PSTPath = Left($aPSTPaths[$], $_)		; get path part 
 
  ; determine if the PST is on a network drive.  
  For $_ = 0 to UBound($aMapped)
    If $aMapped[$_][0] = $Drv
      $IsNet = $_     				; is network! 
    EndIf
  Next
 
  ; Decide if this file should be processed - NetFlag=0 for all, or NetFlag=1 AND IsNet >= 0 
  If Not $NetFlag Or $NetFlag And $IsNet >= 0
 
    ; Display the PST name 
    ' ' Left($Drv + $PSTName + '                         ', 25) '- '
 
    ; Disassociate by deleting the registry key 
    'Disassociate'
    $_ = DelTree($aPSTKeys[$])
 
    ; Rename the file 
    $NewPSTName = @USERID + '_' + Join(Split(Join(Split($PSTName, ' '), '_'), '.pst'), '.stp')
    '; move'
    Move $PSTPath + $PSTName $PSTPath + $NewPSTName
 
    ; convert local to UNC for logging 
    If $IsNet = -1				; local 
      $aPSTPaths[$] = '\\' + @WKSTA + '\' + Left($aPSTPaths[$], 1) + Chr(36) + SubStr($PSTPath, 3) + $NewPSTName
    Else
      $aPSTPaths[$] = $aMapped[$IsNet][1] + SubStr($PSTPath, 3) + $NewPSTName
    EndIF
 
    ; write log for processed PST files - these files will be processed offline to move the 
    ; renamed PST files to a central folder for processing 
    '; write log'
    $_ = RedirectOutput($LogPath + '\' + @WKSTA + '_PST.txt')
    $aPSTPaths[$] ?
    $_ = RedirectOutput('')
 
    ' - Done!' ?
 
  EndIf
 
Next
 
 
 
Exit 0
 
 
 
; convert Unicode strings to ASCII 
Function UtoA($_String)
 
  ; return if string is empty 
  If Not $_String Exit 0 EndIf
 
  Dim $_S, $_I				; temp string, index pointer 
 
  ; get each character pair as hex and convert to ASCII character 
  For $_I = 1 to Len($_String) Step 4
    $_S = $_S + Chr(Val('&' + SubStr($_String, $_I, 2)))
  Next
 
  $UtoA = $_S
  Exit 0
 
EndFunction
 
 
 
;; 
;;====================================================================== 
;; 
;;FUNCTION       TimeDiff() 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        2.2 / 2007/10/14 
;;		 Modified to increase accuracy, permit fracional second calculations 
;;		 2.1 / 2007/03/17 
;;               added "now" and "today" options for both start and end times 
;;               2.0 / 2006/11/20 
;;               Changes for code efficiency; added defaults for midnight  
;; 
;;ACTION         Calculates the time difference between two given date/time strings 
;; 
;;SYNTAX         TimeDiff(Start [, End] [, Format] [, MSec]) 
;; 
;;PARAMETERS     Start  - REQUIRED, String value representing the start timestamp 
;;                 Format yyyy/mm/dd hh:mm:ss 
;; 
;;               End    - OPTIONAL, Defaults to "now" 
;;		   String value representing the ending time 
;;                 Format yyyy/mm/dd hh:mm:ss 
;;		   Can be the special value "now" for the current date/time, or "today" 
;;                 for midnight of the current day. 
;; 
;;                 When the time value is not specified, it defaults to 00:00:00.000 (midnight) 
;; 
;;		 Format - OPTIONAL, one of: 
;;		  "m" - return minutes 
;;		  "h" - return hours 
;;		  "d" - return days 
;;		  "y" - return years 
;;		 When a format value is specified, it returns the fractional part (ie 0.5 days for 12 hours). 
;; 
;;		 MSec	- OPTIONAL, True if the fractional seconds should be returned. Default 
;;		  is false, returning whole seconds, to maintain compatibility with earlier versions. 
;;		  MSec only affects the return of fractional seconds, not fractional parts of other time formats. 
;; 
;;REMARKS        Returns a value representing the difference in time between two date/time 
;;		 strings. Assumes that "Start" is in the past, but will properly return a 
;;		 negative value if it is in the future. 
;; 
;;RETURNS        Double - difference between Start and End timestamps in seconds 
;; 
;;DEPENDENCIES   None 
;; 
;;TESTED WITH    Kix 4.2+, NT4, W2K, WXP, W2K3 
;; 
;;EXAMPLES       If TimeDiff(GetFileTime('SomeFile.txt'),  'now', 'h') > 48 
;;		   "File is more than 2 days old!" ? 
;;		 EndIf 
; 
Function TimeDiff($_Start, OPTIONAL $_End, OPTIONAL $_Fmt, OPTIONAL $_MSec)
 
  Dim $_, $_SDate, $a_Start, $_EDate, $a_End, $_Duration
 
  ; Check for special START parameters 
  Select
   Case $_Start = 'now'
    $_Start = @DATE + ' ' + @TIME + '.' + @MSECS
   Case $_START = 'today'
    $_Start = @DATE + ' 00:00:00.000'
  EndSelect
 
  ; Check for special END parameters 
  Select
   Case $_End = 'now' Or $_End = '' 
    $_End = @DATE + ' ' + @TIME + '.' + @MSECS
   Case $_End = 'today'
    $_End = @DATE + ' 00:00:00.000'
  EndSelect
 
  ; Validate parameters 
  ; Parameters passed are "yyyy/mm/dd hh:mm:ss[.sss]" - make sure the default time is added 
  $a_Start = Split(Join(Split(Join(Split($_Start + ' 00:00:00.000', '/'), ' '), ':'), ' '), ' ', 6)
  If UBound($a_Start) <> 5 Exit 87 EndIf		; bad start time parameter 
  For $_ = 0 to 5
    $a_Start[$_] = CDbl($a_Start[$_])		; convert to numeric values 
  Next
 
  $a_End = Split(Join(Split(Join(Split($_End + ' 00:00:00.000', '/'), ' '), ':'), ' '), ' ', 6)
  If UBound($a_End) <> 5 Exit 87 EndIf		; bad start time parameter 
  For $_ = 0 to 5
    $a_End[$_] = CDbl($a_End[$_])		; convert to numeric values 
  Next
 
  ; Convert dates to Days, then convert to seconds and add the time value 
  If $a_Start[1] < 3
    $a_Start[1] = $a_Start[1] + 12
    $a_Start[0] = $a_Start[0] - 1
  EndIf
  $_SDate = $a_Start[2] + ( 153 * $a_Start[1] - 457 ) / 5 + 365 * $a_Start[0] + $a_Start[0] / 4 - $a_Start[0] / 100 + $a_Start[0] / 400 - 306
  $_SDate = CDbl($_SDate) * 86400.0
  $_SDate = $_SDate + $a_Start[3] * 3600 + $a_Start[4] * 60 + $a_Start[5]
 
  If $a_End[1] < 3
    $a_End[1] = $a_End[1] + 12
    $a_End[0] = $a_End[0] - 1
  EndIf
  $_EDate = $a_End[2] + ( 153 * $a_End[1] - 457 ) / 5 + 365 * $a_End[0] + $a_End[0] / 4 - $a_End[0] / 100 + $a_End[0] / 400 - 306
  $_EDate = CDbl($_EDate) * 86400.0
  $_EDate = $_EDate + $a_End[3] * 3600 + $a_End[4] * 60 + $a_End[5]
 
  ; Get the duration between the timestamps 
  $_Duration = CDbl($_EDate - $_SDate)
 
  ; Trim fractional seconds if the MSec flag wasn't set 
  ; Value returned is whole seconds 
  If Not $_MSec
    $_Duration = CInt($_Duration)
  EndIf
 
  ; Return data as a Double - seconds (default), hours, minutes, days, or years 
  Select
    Case $_Fmt = 'm'	; minutes 
      $TimeDiff = $_Duration / 60.0
    Case $_Fmt = 'h'	; hours 
      $TimeDiff = $_Duration / 3600.0
    Case $_Fmt = 'd'	; days 
      $TimeDiff = $_Duration / 86400.0
    Case $_Fmt = 'y'	; years 
      $TimeDiff = $_Duration / 31536000.0
    Case 1
      $TimeDiff = $_Duration
  EndSelect
 
  Exit 0
 
EndFunction
 
 
 
 
;; 
;;====================================================================== 
;; 
;;FUNCTION       WMIMappedDrives() 
;; 
;;ACTION         Return a list of mapped drives from a computer 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        1.0 / 2008/03/24 
;; 
;;SYNTAX         WMIMappedDrives([, AuthPtr]) 
;; 
;;PARAMETERS     AuthPtr - OPTIONAL - pre-authenticated WMI object pointer 
;;                 Use WMIAuthentication() udf to create the AuthPtr value 
;;                 AuthPtr is not needed if user has admin rights 
;; 
;;REMARKS        By default, returns a detailed list of all processes from the local computer. An 
;;		   alternate computer can be specified, as can a specific process. When specifying 
;;		   processes, you can use a name (cmd.exe) or process ID. If you specify a name, all 
;;		   processeses matching that name will be returned, while specifying a PID will return 
;;		   exactly one element that matches that process (if it was found) 
;;		   If no match is found, the function exits with status 2 (file not found) 
;; 
;;RETURNS        Array of drive letter / UNC Path arrays 
;; 
;;DEPENDENCIES   WMI,  
;; 
;;TESTED WITH    W2K, WXP, W2K3, Vista, x64 
;; 
;;EXAMPLES        
; 
Function WMIMappedDrives(OPTIONAL $_pAuth)
 
  Dim $_objWMIService, $_colItems, $_objItem	; WMI object vars 
  Dim $_Line					; line string 
  Dim $_aTmp[0], $_I				; return array, index 
  Dim $_					; temp var 
 
  $_I = -1
 
  ; If a pre-authenticated WMI object pointer was provided, use it, otherwise create a new object pointer 
  If $_pAuth
    $_objWMIService = $_pAuth
  Else
    $_objWMIService = GetObject('winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2')
    If @ERROR Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
  EndIf
 
  ; get the collection of process objects 
  $_colItems = $_objWMIService.ExecQuery("Select * from Win32_MappedLogicalDisk",,48)
  If @ERROR   $_colItems = 0 Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
 
  ; Enumerate the collection of process data 
  For Each $_objItem in $_colItems
    $_I = $_I + 1
    ReDim Preserve $_aTmp[$_I]
    $_aTmp[$_I] =  $_objItem.Name, $_objItem.ProviderName
  Next
 
  ; return the array, close the collection, and gripe if no items were found 
  $WMIMappedDrives = $_aTmp
  $_colItems = 0
  Exit 0
 
EndFunction
 
 
 
;; 
;;====================================================================== 
;; 
;;FUNCTION       WMIProcessKill() 
;; 
;;ACTION         Terminates a specific process by numeric PID 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        1.0 / 2007/10/20 
;; 
;;SYNTAX         WMIProcessKill(Process [, Computer] [, AuthPtr]) 
;; 
;;PARAMETERS     Process - PID of process to terminate 
;; 
;;		 Computer - OPTIONAL, Name of computer to target 
;; 
;;               AuthPtr - OPTIONAL - pre-authenticated WMI object pointer 
;;                 Use WMIAuthentication() udf to create the AuthPtr value 
;;                 AuthPtr is not needed if user has admin rights 
;; 
;;REMARKS        Terminates the process after verifying it exists 
;; 
;;RETURNS        1 (success) or 0 (failure) 
;; 
;;DEPENDENCIES   WMI 
;; 
;;TESTED WITH    W2K, WXP, W2K3, Vista, x64 
;; 
;;EXAMPLES        
; 
Function WMIProcessKill($_Process, Optional $_Computer, OPTIONAL $_pAuth)
 
  Dim $_objWMIService, $_colItems, $_objItem	; WMI object vars 
  Dim $_					; temp var 
  Dim $_Err					; error code 
 
  ; Must be a single numeric value to terminate 
  If Val($_Process) <> $_Process
    $WMIProcessKill = 0
    Exit 87
  EndIf
 
  ; insure a properly formatted computer name, default to local computer is not specified 
  $_Computer = IIf(Not $_Computer, '.', Join(Split($_Computer,'\'),''))
 
  ; If a pre-authenticated WMI object pointer was provided, use it, otherwise create a new object pointer 
  If $_pAuth
    $_objWMIService = $_pAuth
  Else
    $_objWMIService = GetObject('winmgmts:{impersonationLevel=impersonate}!\\' + $_Computer + '\root\cimv2')
    If @ERROR Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
  EndIf
 
  ; get the collection of process objects  
  $_colItems = $_objWMIService.ExecQuery("Select * from Win32_Process Where ProcessID=" + '"' + $_Process + '"',,48)
  If @ERROR   $_colItems = 0 Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
 
  $_Err = 2	; prepare for not found 
 
  ; Enumerate the collection of process data 
  For Each $_objItem in $_colItems
    $_ = $_objItem.Terminate
    $_Err = @ERROR
  Next
  $_colItems = 0
 
  ; return appropriate values 
  $WMIProcessKill = Not $_Err
  Exit $_Err
 
EndFunction
 
 
 
;; 
;;====================================================================== 
;; 
;;FUNCTION       WMIProcessList() 
;; 
;;ACTION         Return a list of process info from a computer 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        1.0 / 2007/10/12 
;;		 Written as a replacement for PSList(), which uses SysInternals PSList.exe 
;; 
;;SYNTAX         WMIProcessList([Process] [, Computer] [, AuthPtr]) 
;; 
;;PARAMETERS     Process - return information about a specific process (group) by name 
;;		   or individual process ID (PID) 
;; 
;;		 Computer - OPTIONAL, Name of computer to query 
;; 
;;               AuthPtr - OPTIONAL - pre-authenticated WMI object pointer 
;;                 Use WMIAuthentication() udf to create the AuthPtr value 
;;                 AuthPtr is not needed if user has admin rights 
;; 
;;REMARKS        By default, returns a detailed list of all processes from the local computer. An 
;;		   alternate computer can be specified, as can a specific process. When specifying 
;;		   processes, you can use a name (cmd.exe) or process ID. If you specify a name, all 
;;		   processeses matching that name will be returned, while specifying a PID will return 
;;		   exactly one element that matches that process (if it was found) 
;;		   If no match is found, the function exits with status 2 (file not found) 
;; 
;;RETURNS        Array of comma-delimited values, one element per process: 
;;			Process Name 
;;			Process ID (PID) 
;;			Thread Count 
;;			Handle Count 
;;			Memory Usage (Bytes) 
;;			User Time	D:HH:MM:SS.sss format 
;;			Kernel Time	D:HH:MM:SS.sss format 
;;			Elapsed Time	D:HH:MM:SS.sss format 
;; 
;;DEPENDENCIES   WMI, TimeDiff() external UDF 
;; 
;;TESTED WITH    W2K, WXP, W2K3, Vista, x64 
;; 
;;EXAMPLES        
; 
Function WMIProcessList(OPTIONAL $_Process, Optional $_Computer, OPTIONAL $_pAuth)
 
  Dim $_objWMIService, $_colItems, $_objItem	; WMI object vars 
  Dim $_Line					; line string 
  Dim $_aTmp[0], $_I				; return array, index 
  Dim $_					; temp var 
  Dim $_BTime, $_CTime				; boot and current times from target system 
 
  $_I = -1
 
  ; insure a properly formatted computer name, default to local computer is not specified 
  $_Computer = IIf(Not $_Computer, '.', Join(Split($_Computer,'\'),''))
 
  ; If a pre-authenticated WMI object pointer was provided, use it, otherwise create a new object pointer 
  If $_pAuth
    $_objWMIService = $_pAuth
  Else
    $_objWMIService = GetObject('winmgmts:{impersonationLevel=impersonate}!\\' + $_Computer + '\root\cimv2')
    If @ERROR Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
  EndIf
 
  ; Get the current and boot times from the client system 
  $_colItems = $_objWMIService.ExecQuery("Select * from Win32_OperatingSystem",,48)
  For each $_objItem in $_colItems
    $_BTime = $_objItem.LastBootUpTime			; host-local boot time 
    $_CTime = $_objItem.LocalDateTime			; host-local current time 
  Next
 
  $_colItems = 0
  ; convert to a normalized time string 
  $_CTime = SubStr($_CTime, 1,4) + '/' + SubStr($_CTime, 5,2) + '/' +SubStr($_CTime, 7,2) + ' '
         + SubStr($_CTime, 9,2) + ':' + SubStr($_CTime, 11,2) + ':' + SubStr($_CTime, 13,6)
 
  ; get the collection of process objects 
  $_colItems = $_objWMIService.ExecQuery("Select * from Win32_Process",,48)
  If @ERROR   $_colItems = 0 Exit Val('&' + Right(DecToHex(@ERROR), 4)) EndIf
 
  ; Enumerate the collection of process data 
  For Each $_objItem in $_colItems
    ; add the data to the array if no process is specified, or if a specific process name or ID is specified 
    If Not $_Process Or $_Process = $_objItem.Name Or $_Process = $_objItem.ProcessID 
      $_Line =  $_objItem.Name
      $_Line = $_Line + ',' + $_objItem.ProcessID
      $_Line = $_Line + ',' + $_objItem.Priority
      $_Line = $_Line + ',' + $_objItem.ThreadCount
      $_Line = $_Line + ',' + $_objItem.HandleCount
      $_Line = $_Line + ',' + $_objItem.WorkingSetSize
      ; convert the following to d:hh:mm:ss.sss format 
      $_Line = $_Line + ',' + _WMIPLTC(CDbl($_objItem.UserModeTime) * 0.0000001)
      $_Line = $_Line + ',' + _WMIPLTC(CDbl($_objItem.KernelModeTime) * 0.0000001)
      ; Use the system boot time if creation date is not set 
      $_ = IIf($_objItem.CreationDate , $_objItem.CreationDate, $_BTime)
      ; calculate elapsed time and convert to d:hh:mm:ss.sss format 
      $_Line = $_Line + ',' +  _WMIPLTC(_WMIPLET($_, $_CTime))
      ; Update the array 
      $_I = $_I + 1
      ReDim Preserve $_aTmp[$_I]
      $_aTmp[$_I] = $_Line
    EndIf
  Next
 
  ; return the array, close the collection, and gripe if no items were found 
  $WMIProcessList = $_aTmp
  $_colItems = 0
  If $_Process and $_I < 0 Exit 1911 EndIf
  Exit 0
 
EndFunction
 
 
; support function to calculate elapsed time as Seconds 
; Dependent on TimeDiff UDF! 
Function _WMIPLET($_Time, $_CTime)
 
  Dim $_CurrentTime
 
  ; Break into Date and Time parts, including 3 decimal points 
  $_Time = SubStr($_Time, 1,4) + '/' + SubStr($_Time, 5,2) + '/' +SubStr($_Time, 7,2) + ' '
         + SubStr($_Time, 9,2) + ':' + SubStr($_Time, 11,2) + ':' + SubStr($_Time, 13,6)
 
  ; return the value with 3 decimal places 
  $_WMIPLET = TimeDiff($_Time, $_CTime, '', 1); FormatNumber(, 3, -1, 0, 0) 
  Exit 0
 
EndFunction
 
 
; support function to conver the time value (100ns units) to D:H:M:S.s format 
Function _WMIPLTC($_Units)
 
  Dim $_D, $_H, $_M, $_S		; day, hour, minute, & second values 
 
  ; Find d/h/m and subtract result from units 
  $_D = Int($_Units / 86400)	$_Units = $_Units - $_D * 86400
  $_H = INT($_Units/3600)	$_Units = $_Units - $_H * 3600
  $_M = INT($_Units/60)
  $_S = FormatNumber($_Units - $_M * 60, 3, -1, 0, 0)
 
  ; return a time string 
  $_WMIPLTC = '' + $_D + ':' + $_H + ':' + $_M + ':' + $_S
  Exit 0
 
EndFunction
 
 
 
 


AllenAdministrator
(KiX Supporter)
2009-05-20 01:01 AM
Re: Searching PSTs on PC's

I got a little curious with your script Glenn... and for the life of me can't make sense of how your code is pulling multiple paths out of that one registry value. This part:

 Code:
; find the PST files associated with this profile - need to enumerate each subkey and find the 001f6700 value 
$eIndex = 0
$aIndex = -1
$EKey = EnumKey($WKey, $eIndex)
While Not @ERROR
  $TVal = UtoA(ReadValue($WKey + $EKey, '001f6700'))
  If $TVal
    $aIndex = $aIndex + 1
    ReDim Preserve $aPSTPaths[$aIndex]
    ReDim Preserve $aPSTKeys[$aIndex]
    $aPSTPaths[$aIndex] = $TVal
    $aPSTKeys[$aIndex] = $WKey + $EKey
  EndIf
  $eIndex = $eIndex + 1
  $EKey = EnumKey($WKey, $eIndex)
Loop


In my outlook profile, I have two pst files. Both are located in the one 001f6700 value. And it appears to pull them out correctly, but I just can't see how you are doing that. It seems like it would only find one value. Would you mind documenting that or maybe some different var names?


Richard H.Administrator
(KiX Supporter)
2009-05-20 09:44 AM
Re: Searching PSTs on PC's

 Originally Posted By: Allen
Both are located in the one 001f6700 value


Are you sure?

There are multiple 001f6700 values under seperate subkeys and the script collects them together in an array.

Here's that bit of code simplified:
 Code:
; find the PST files associated with this profile - need to enumerate each subkey and find the 001f6700 value 
$eIndex = 0
$EKey = EnumKey($WKey, $eIndex)
While Not @ERROR
  $TVal = UtoA(ReadValue($WKey + $EKey, '001f6700'))
  If $TVAL $EKey+": "+$TVAL+@CRLF EndIf
  $eIndex = $eIndex + 1
  $EKey = EnumKey($WKey, $eIndex)
Loop


You should find that $EKey changes for each PST found.


AllenAdministrator
(KiX Supporter)
2009-05-20 02:40 PM
Re: Searching PSTs on PC's

Last night, I would have said I was sure... but this morning I checked again... and now it all makes sense, and all the keys are there. Sorry for the alarm. (I'm one step closer to senilism \:\( )

Glenn BarnasAdministrator
(KiX Supporter)
2009-05-20 03:22 PM
Re: Searching PSTs on PC's

Here's a premature "Welcome" to that club.. I looked at that and thought "I wrote that?".. didn't match with my home workstations and then I realized "ah - need Exchange!"

Looking at the keys in the office, I see that Richard is suffering from far less senility than we are \:D - there are indeed multiple keys.

Glenn


NTDOCAdministrator
(KiX Master)
2009-06-27 02:39 AM
Re: Searching PSTs on PC's

Great script Glenn, but if you're using XP or above then you should be using the Volume Shadow Copy Service which does not require you to close Outlook or anything. Just locate the path and name for the source, then run your backup utility or NTBACKUP (on XP Pro) and copy to your destination.

Glenn BarnasAdministrator
(KiX Supporter)
2009-06-27 03:08 AM
Re: Searching PSTs on PC's

We closed Outlook because we were making the PST disappear from the user. As the header says - after closing Outlook, we disassociate the PST from Outlook, rename it, and record the location so we can mess with it later (import it into the archive system) without the user being able to manipulate it.

If we weren't taking the PST away from the user, then your idea (and not closing Outlook) would make sense.. \:\)

Glenn


NTDOCAdministrator
(KiX Master)
2009-06-29 08:34 PM
Re: Searching PSTs on PC's

Thanks for the input Glenn. Guess I missed that part, thought you were just backing up. As usual, some great code. Now I just need to go through it and trim it down a bit to use for an automated backup to external USB drives each day using NTBACKUP. We have half a dozen users with multiple GB PST files that are local, but still need to be backed up so I don't catch grief for it later on for not doing it. I could manually create the folders and find the PST files, but doing it in an automated fashion seem fun.






NTDOCAdministrator
(KiX Master)
2009-07-14 03:07 AM
Re: Searching PSTs on PC's

Well I was modifying Glenn's code to hook into a semi-automated PST backup to external USB drive and found that you can't backup a single file via the command line. You have to use a file that ntbackup reads. It is a .BKS file and you call it with an @filepath+filename.bks

So now I have to add a bit more code to actually write the file first and then call it. You can do folder backups with all command line or systemstate but not single files.

But, thanks for putting your code together so I could rip it apart for my own use Glenn.


Radimus
(KiX Supporter)
2009-07-14 05:07 AM
Re: Searching PSTs on PC's

PSTs are BBBBAAAADDDDDD.

We (my company) has spent the bulk of the last year disabling PST and importing PSTs into a server based archive (Symantec Enterprise Vault) after having to search out and preserve PSTs and search them for legal discovery.

I should have some code that reads the user's messaging profile for PSTs and then robocopies them to a central store, but certainly nothing as sophisticated as Glenn's code.


NTDOCAdministrator
(KiX Master)
2009-07-14 01:32 PM
Re: Searching PSTs on PC's

I don't like them either, but I don't own or run the Company. The solution you speak of cost thousands of dollars and that just isn't happening.

I'm having a hard time trying to get them to reduce their mailboxes. They will not allow me to put a limit policy in place and currently I have some users with about 15GB in their InBox - I have to do something or their going to run the Mail Server out of space in the next few months. Though maybe I should let them and tell them "I told you so".

I've got the code just about done to do automated backups. Should hopefully have it done either tomorrow or the next day. I must be missing something as you're now the second person to suggest RoboCopy and I'm a big fan of RoboCopy but a PST file should be locked for write and not copyable by RoboCopy. Let alone you'd still need to either manually find and set or automated find and set the path and files.



AllenAdministrator
(KiX Supporter)
2009-07-14 02:30 PM
Re: Searching PSTs on PC's

I'd be interested too about copying / backing up an open pst. One of my customers has tons of those things. Backup Exec sees them as corrupted files.

At one point, I was going down the road of trying to determine if any pst files were open, and to either close the app or disconnect the shared directory. Now what did I do with that code?... and what was the result? (Don't mind me, just having one of my many senior moments. )


Radimus
(KiX Supporter)
2009-07-14 08:25 PM
Re: Searching PSTs on PC's

I understand about the cost and it isn't for everyone, but we had 2 lawsuits that required us to produce every email from every manager and 3 departments (about 150 - 200 staff in 13 remote sites) containing X search strings.

We also have to preserve their PSTs in their original form at that time (create disk images if on local drives)

a HUGE PITA taking up the time of a dozen IT staff...

It wasn't long until the decision was made to go with centralized storage... there is always another lawsuit and discovery process.


NTDOCAdministrator
(KiX Master)
2009-07-16 03:42 AM
Re: Searching PSTs on PC's

What a pain. So it turns out that Microsoft NTBACKUP uses Unicode UTF-16LE NO BOM for creating and reading a .BKS file.

So far I've not been able to get VBScript with FSO to create one. If anyone else has had any luck with this please let me know and share the code.

I can use UltraEdit to save the file as Unicode UTF-16LE NO BOM and NTBACKUP will use it, but I'd really like to finish this off with an automated script if possible. Otherwise I'll have to build the BKS file manually for everyone that I want to set this backup for.


AllenAdministrator
(KiX Supporter)
2009-07-16 05:50 AM
Re: Searching PSTs on PC's

Do you know about this: CreateBKS() - http://www.kixtart.org/forums/ubbthreads...true#Post120687

NTDOCAdministrator
(KiX Master)
2009-07-16 07:05 PM
Re: Searching PSTs on PC's

Dependencies: KiXforms .net

Don't want it to depend on installing KiXforms. Pretty much would need to be either KiX or VBScript since KiX is in Netlogon and WSH is native

Thanks for sharing the idea though Allen.



Byte-order mark
http://en.wikipedia.org/wiki/Byte-order_mark

UTF-8
http://en.wikipedia.org/wiki/UTF-8


UTF-16/UCS-2
http://en.wikipedia.org/wiki/UTF-16/UCS-2
 Quote:
UTF-16 is the native internal representation of text in the Microsoft Windows 2000/XP/2003/Vista/CE; Qualcomm BREW operating systems; the Java and .NET bytecode environments; Mac OS X's Cocoa and Core Foundation frameworks; and the Qt cross-platform graphical widget toolkit


NTDOCAdministrator
(KiX Master)
2009-07-16 07:12 PM
Re: Searching PSTs on PC's

Just out of curiosity I'll try and use KiXforms there to create the file but my guess is it can not build it correctly either. Back when that was written I think NTBACKUP could use the normal notepad UNICODE file, but along the way Microsoft changed it.