Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Simple directory backup/file restore


  • Please log in to reply
12 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Here is a script which provides the basic backup, restore functionality. It is very short considering the functionality it offers: If a file was already backed up (same name, location and modification time), it gets ignored. If a newer version exists, the previous backup is first moved into a subdirectory of the backup directory, so in the top level there are always the most recent backup copies. The user provides a limit for the maximum number of earlier versions. If it is exceeded, the oldest backup is deleted. If its subdirectory becomes empty, we remove it.

The main difficulty is mirroring the directory structure of the source directory. If whole directory branches get copied, we might store unchanged files in many backup copies, a waste of disk space. Otherwise, moving older backups in the directory tree and remove/create directory branches is a nightmare. We employed instead a cute trick: code the path of each file into its name (by replacing the \ characters with `, assuming that valid filenames don't contain ` characters.) This way all the backup files reside in the top level of the backup directory. When a new copy forces and older backup file to give space, we create a subdirectory for it in the backup directory with a name form the current UTC date and time. This way there will be no name conflicts and the backup directory structure remains always at most two levels deep.

The BACKUP script starts with asking the user to select the source and backup directories, enter the limit of the number of older backup files kept, the include file pattern and the list of excluded file patterns. If everything was right, the backup procedure starts: each file is taken in the source directory and its subdirectories, which is matches the include pattern and does not match any of the exclude patterns. If it has a backup already, take the next file. If it has an earlier backup, move that in a new subdirectory in the backup directory to make room. If the limit of the number of old backups is exceeded, remove the oldest backup of this file.
Loop
{
   FileSelectFolder dir_source, *C:\btest, 2, Select the directory you want to backup
   FileSelectFolder dir_backup, *C:\backup,3, Select the backup directory
   InputBox max_copies, Max Copies, How many older`nbackup copies kept?,,180,160,,,,,9
   InputBox include, Include, Include file pattern ,,180,140,,,,,*.*
   InputBox exclude, Exclude, Exclude file patterns,,240,140,,,,, .exe, temp., .tmp
   MsgBox 33,,Source directory = %dir_source%`nBackup directory = %dir_backup%`nMaximum #copies = %max_copies%`nIncludde = %include%`nExclude = %exclude%
   IfMsgBox OK
      Break
}

StringReplace exclude, exclude, %A_SPace%,,All
FormatTime dir_bb,%A_NowUTC%,yyyy.MM.dd-HH.mm.ss
FileCreateDir %dir_backup%\%dir_bb%    ; directory for older backup files named with time

Loop %dir_source%\%include%,,1         ; recurse into subdirectories
{
   If A_LoopFileName contains %exclude%
      Continue
   path = %A_LoopFileFullPath%         ; assumed path does not contain "`"
   ToolTip Processing %path%
   StringReplace file, path, \, ``, All
   StringReplace file, file, :,, All   ; \ -> `, delete ":"
   IfExist %dir_backup%\%file%
   {
      FileGetTime sourceTime, %path%
      FileGetTime backupTime, %dir_backup%\%file%
      IfEqual sourceTime,%backupTime%, Continue
      FileMove %dir_backup%\%file%, %dir_backup%\%dir_bb%\%file%
      Count = 0
      dir_Oldest = z                   ; z is larger than any dir_bb name
      Loop %dir_backup%\%file%,,1
      {
         Count++                       ; count copies in subdirectories
         If A_LoopFileDir < %dir_Oldest%
            dir_Oldest = %A_LoopFileDir%
      }
      If Count > %max_copies%
      {
         FileDelete %dir_Oldest%\%file%
         FileRemoveDir %dir_Oldest%    ; remove if empty
      }
   }
   FileCopy %path%, %dir_backup%\%file%
   ToolTip
}

FileRemoveDir %dir_backup%\%dir_bb%    ; remove if empty
The RESTORE script is even simpler. The user selects the backup files to be restored. They get processed one-by-one. Their name is converted back to paths and they get copied over to their original place, overwriting a file with the same name there, if any.
FileSelectFile FileList, M3, C:\backup, Select files to be restored
Loop Parse, FileList, `n
{
   IfEqual A_Index,1, {
      path = %A_LoopField%
      Continue
   }
   StringReplace file, A_LoopField, ``, :\
   StringReplace file, file, ``, \, All
   ToolTip Restoring %path%\%A_LoopField%`nTo %file%
   FileCopy %path%\%A_LoopField%, %file%, 1
   ToolTip
}
The backup directory should be a compressed Windows directory to trade storage space to access time.

Possible extensions:
- Error checking
- Single GUI for selecting the backup directories, options
- Multiple include patterns with wildcards (e.g. Documents\*.doc, D:\Projects\*.ahk)
- Exclude patterns with wildcards (e.g. ~W*.tmp, $??$.t*)
- File-selection at restore performed in original path\filename format, with collapsible subdirectories
- Restore file patterns, time ranges.
- List older backups of a given file sorted by their date/time.
- Compact the backup subdirectories: if there is room for a moved backup file in the previous subdirectory, use that instead, as the destination. This way there will be fewer subdirectories but the older backups are still reside in older subdirectories (the ordering is preserved).
- Viewer for the file to be restored...

specialGuest
  • Guests
  • Last active:
  • Joined: --

It is very short considering the functionality it offers

.
That is definitely true - and as everybody expected, who read the author of this post - an extremely elegant solution.
Until now, I only tested the backup-part, it works great!
Thank you for sharing all those good ideas with us.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
In this version, when a new copy forces and older backup file to give space, we create a subdirectory for it in the backup directory with a name counted from 00000. There will be no name conflicts and the backup directory structure remains always at most two levels deep. If there is room for a backup file to be moved in the previous subdirectory, we use that instead, as the destination. This way the number of subdirectories can be reduced. Older backups reside in subdirectories of smaller name (the ordering is preserved).

Default values are stored in the BKup.ini file, which should be edited to the users' needs:

[Defaults]
SourceDirectory = C:\btest
BackupDirectory = C:\backup
Max_Copies = 3
Include = *.*
Exclude = .exe, temp., .tmp

The BACKUP script pops up a GUI window where the user can select (or type in) the source and backup directories, enter the limit of the number of older backup files kept, the include file pattern (currently only one, but with wildcards, like "*.doc") and the list of excluded file patterns (comma separated, but w/o wildcards, like ".exe, temp., .tmp" [use period!]). If the user accepts the settings (possibly after saving them for defaults), the backup procedure starts: from the source directory and its subdirectories each file is taken, which matches the include pattern and its name does not contain any of the exclude patterns. If it has a backup already, skip to the next file. If it has an earlier backup, move that in a subdirectory of the backup directory to make room. If the limit of the number of old backups is exceeded, remove the oldest backup of this file.
#SingleInstance Force
IniFile = BKup.ini

Gui Font, s10, Arial
Gui Add, Text,   X010 Y020, Source Folder
Gui Add, Button, X185 Y013 W125 gSelectSource, Select
Gui Add, Edit,   X010 Y050 W300 vDir_source
Gui Add, Text,   X010 Y090, Include Pattern
Gui Add, Edit,   X010 Y120 W300 vinclude
Gui Add, Text,   X010 Y160, Exclude Pattern
Gui Add, Edit,   X010 Y190 W300 vexclude

Gui Add, Text,   X360 Y020, Backup Folder
Gui Add, Button, X535 Y013 W125 gSelectBackupDir, Select
Gui Add, Edit,   X360 Y050 W300 vDir_backup
Gui Add, Text,   X360 Y090, Max Copies
Gui Add, Edit,   X360 Y120 W125 vmax_copies
Gui Add, UpDown, Range1-999

Gui Add, Button, X533 Y088 W125, Get Defaults
Gui Add, Button, X533 Y138 W125, Set Defaults
Gui Add, Button, X360 Y188 W125 Default, &Accept
Gui Add, Button, X533 Y188 W125, &Cancel

GoSub ButtonGetDefaults
Gui Show, W675 H240, Backup
Return

SelectSource:
   Gui +OwnDialogs                        ; Following dialog must be closed before using main window
   FileSelectFolder Folder, *%Dir_source%, 2, Select the Source folder:
   if not Folder                          ; The user canceled the dialog
      Return
   Dir_source = %Folder%
   GuiControl,,Dir_Source, %Dir_source%
Return

SelectBackupDir:
   Gui +OwnDialogs                        ; Following dialog must be closed before using main window
   FileSelectFolder Folder, *%Dir_backup%, 3, Select the Backup folder:
   if not Folder                          ; The user canceled the dialog
      Return
   Loop %Folder%\*.*, 2                   ; Reject non-backup folders
   {
      If A_LoopFileName is Integer
         Continue
      If A_LoopFileName not contains ``
      {
         MsgBox NOT A BACKUP FOLDER
         Return
      }
   }
   Dir_backup = %Folder%
   GuiControl,,Dir_Backup, %Dir_backup%
Return

ButtonGetDefaults:
   IniRead Dir_source, %IniFile%, Defaults, SourceDirectory
   IniRead Dir_backup, %IniFile%, Defaults, BackupDirectory
   IniRead max_copies, %IniFile%, Defaults, Max_Copies
   IniRead include,    %IniFile%, Defaults, Include
   IniRead exclude,    %IniFile%, Defaults, Exclude
   GuiControl,,Dir_Backup, %Dir_backup%
   GuiControl,,Dir_Source, %Dir_source%
   GuiControl,,include,    %include%
   GuiControl,,exclude,    %exclude%
   GuiControl,,max_copies, %max_copies%
Return

ButtonSetDefaults:
   Gui Submit, Nohide
   IniWrite %Dir_source%, %IniFile%, Defaults, SourceDirectory
   IniWrite %Dir_backup%, %IniFile%, Defaults, BackupDirectory
   IniWrite %max_copies%, %IniFile%, Defaults, Max_Copies
   IniWrite %include%,    %IniFile%, Defaults, Include
   IniWrite %exclude%,    %IniFile%, Defaults, Exclude
Return

GuiClose:
GuiEscape:
ButtonCancel:
   Gui Destroy
   ExitApp
Return
                                          ; PERFORM THE BACKUP
ButtonAccept:
   Gui Submit                             ; assign gui variables
   Gui Destroy
   Dir_backup = %Dir_backup%\
   StringReplace Dir_backup, Dir_backup, \\, \
   Dir_source = %Dir_source%\
   StringReplace Dir_source, Dir_source, \\, \
   StringReplace exclude, exclude, %A_SPace%,,All
   StringReplace exclude, exclude, %A_Tab%,,All

   Loop %Dir_backup%*, 2                  ; look at subdirectories
      if dir_bb < %A_LoopFileName%
         dir_bb = %A_LoopFileName%        ; latest subdirectory
   IfEqual dir_bb,, {
      Dir_prev = %Dir_backup%             ; if none: previous <- backup root
      dir_bb = 00000
   }
   Else {
      Dir_prev = %Dir_backup%\%dir_bb%\
      SetFormat Float, 05.0
      dir_bb += 1.0
   }
   FileCreateDir %Dir_backup%%dir_bb%     ; directory for older backup files

   Loop %Dir_source%%include%,,1          ; recurse into subdirectories
   {
      If A_LoopFileName contains %exclude%
         Continue
      path = %A_LoopFileFullPath%         ; assumed path does not contain "`"
      ToolTip Processing %path%
      StringReplace file, path, \, ``, All
      StringReplace file, file, :,, All   ; \ -> `, delete ":"
      IfExist %Dir_backup%%file%
      {
         FileGetTime sourceTime, %path%
         FileGetTime backupTime, %Dir_backup%%file%
         IfEqual sourceTime,%backupTime%, Continue
         IfExist %Dir_prev%%file%         ; true if no previous subdir exists
            FileMove %Dir_backup%%file%, %Dir_backup%%dir_bb%\%file%
         Else
            FileMove %Dir_backup%%file%, %Dir_prev%%file%
         Count = 0
         dir_Oldest = z                   ; z is larger than any dir_bb name
         Loop %Dir_backup%%file%,,1
         {
            Count++                       ; count copies in subdirectories
            If A_LoopFileDir < %dir_Oldest%
               dir_Oldest = %A_LoopFileDir%
         }
         If Count > %max_copies%
         {
            FileDelete %dir_Oldest%\%file%
            FileRemoveDir %dir_Oldest%    ; remove if empty
         }
      }
      FileCopy %path%, %Dir_backup%%file%
   }
   ToolTip ; remove
   FileRemoveDir %Dir_backup%%dir_bb%     ; remove if empty
ExitApp
The RESTORE script has a resizable GUI, based on the ListView demo in the Help. The user loads the files from his backup directories (selecting them one-by-one) to the ListView, and there multiple backup files can be selected (Ctrl/Shift-Click) to be restored. (All the important information is shown about them, like their icon, original location, backup location, size, time of creation and the encoded filename. Double clicking on a file opens it, useful for verification.) Pressing the Restore button the selected files get processed one-by-one. They get copied over to their original place, overwriting a file with the same name there, if any.
#SingleInstance Force
#NoEnv
SetBatchLines -1

IniFile = BKup.ini

Gui +Resize
Gui Font, s9, Arial
Gui Add, Button, Default gButtonLoadFolder, Load a folder
Gui Add, Button, x+20 gButtonClear, Clear List
Gui Add, Button, x+20, Switch View
Gui Add, Button, x+20, Restore
Gui Add, ListView, xm r20 w700 vMyListView gMyListView, Name|Folder|Size KB|Date-Time|Code
LV_ModifyCol(3, "Integer")

ImageListID1 := IL_Create(10,10)       ; Create ImageList so that ListView can display icons
ImageListID2 := IL_Create(10,10,1)     ; A list of large icons to go with the small ones.

LV_SetImageList(ImageListID1)          ; Attach the ImageLists to the ListView...
LV_SetImageList(ImageListID2)          ; so that it can later display the icons

Menu MyContextMenu, Add, Open, ContextOpenFile ; Popup menu to be used as the context menu
Menu MyContextMenu, Add, Properties, ContextProperties
Menu MyContextMenu, Add, Clear from ListView, ContextClearRows
Menu MyContextMenu, Default, Open      ; "Open" bold font indicating that double-click does the same

Gui Show
Return


ButtonLoadFolder:
Gui +OwnDialogs                        ; User must dismiss the following dialog before using main window
IniRead dir_backup, %IniFile%, Defaults, BackupDirectory
FileSelectFolder Folder, *%dir_backup%, 2, Select the Backup folder:
If not Folder                          ; The user canceled the dialog.
   Return

Loop %Folder%\*.*, 2                   ; Reject non-backup folders
{
   If A_LoopFileName is Integer
      Continue
   If A_LoopFileName not contains ``
   {
      MsgBox NOT A BACKUP FOLDER
      Return
   }
}

StringRight, LastChar, Folder, 1
If LastChar = \
   StringTrimRight, Folder, Folder, 1  ; Remove trailing backslash

VarSetCapacity(FileName, 260)          ; Variable need to hold the longest path: ExtractAssociatedIconA()
sfi_size = 352
VarSetCapacity(sfi, sfi_size)

Loop %Folder%\*.*                      ; File names appended to the ListView
{
   FileCode = %A_LoopFileName%         ; Save it to a variable overwritten below
   StringReplace FileName, FileCode, ``, :\
   StringReplace FileName, FileName, ``, \, All
   SplitPath FileName,,,FileExt        ; Get extension
   If FileExt in EXE,DLL,BMP,JPG,JPEG,TIF
   {
      ExtID := FileExt                 ; Special ID as a placeholder.
      IconNumber = 0                   ; Flag it as not found so that EXEs can each have a unique icon.
   } Else {                            ; Non-EXE, so calculate this extension's unique ID.
      ExtID = 0                        ; Initializize to handle short extensions
      StringLeft Ext8, FileExt, 8
      Loop Parse, Ext8                 ; 8 chars < 128 of ext, they fit in positive Int64
         ExtID += Asc(A_LoopField) << 8*A_Index-8
      IconNumber := IconArray%ExtID%   ; Speed up with icons already loaded
   }
   If not IconNumber                   ; Not icon for this extension loaded yet
   {                                   ; Get the icon associated with this file extension
      If !DllCall("Shell32\SHGetFileInfoA", Str,Folder "\" FileCode, Uint,0, Str,sfi, Uint,sfi_size, Uint,0x101) ; 0x101 is SHGFI_ICON+SHGFI_SMALLICON
         IconNumber = 9999999          ; Set it out of bounds to display a blank icon.
      Else {
         hIcon = 0                     ; Add HICON to the small-icon and large-icon lists
         Loop 4
            hIcon += *(&sfi + A_Index-1) << 8*A_Index-8
         IconNumber := 1+DllCall("ImageList_ReplaceIcon", Uint,ImageListID1, Int,-1, Uint,hIcon)
         DllCall("ImageList_ReplaceIcon", UInt,ImageListID2, Int,-1, UInt,hIcon)
         DllCall("DestroyIcon", Uint,hIcon)
         IconArray%ExtID% :=IconNumber ; Cache the icon
      }
   }
   FileGetTime FileTime, %A_LoopFileName%
   FormatTime, FileTime, FileTime, yyyy.MM.dd-HH:mm:ss
   LV_Add("Icon" IconNumber, FileName, A_LoopFileDir, A_LoopFileSizeKB, FileTime, A_LoopFileName)
}
LV_ModifyCol()                         ; Auto-size each column to fit its contents.
LV_ModifyCol(3,70)                     ; Make the Size column at little wider to reveal its header
LV_ModifyCol(4,150)
Return


ButtonClear:
   LV_Delete()                         ; Clear the ListView, but keep icon cache intact
Return

ButtonSwitchView:
   If not IconView
      GuiControl +Icon, MyListView     ; Switch to icon view
   Else
      GuiControl +Report, MyListView   ; Switch back to details view
   IconView := not IconView            ; Invert in preparation for next time
Return

ButtonRestore:
   RowNum = 0                          ; Start the search at the top of the list
   Loop {
      RowNum := LV_GetNext(RowNum)     ; Continue search after that of previous iteration
      If not RowNum                    ; No more selected rows
         break
      LV_GetText(FileName,RowNum, 1)   ; Get the text of the first field
      LV_GetText(FileDir, RowNum, 2)
      LV_GetText(FileCode,RowNum, 5)
      ToolTip Restoring %FileDir%\%FileCode% -> %FileName%
      SplitPath FileName,,Dir0         ; Get the file's original directory
      FileCreateDir %Dir0%             ; Create it, if necessary <-- add other dir selection
      FileCopy %FileDir%\%FileCode%, %FileName%, 1
   }
   ToolTip ; remove
Return

MyListView:
If A_GuiEvent = DoubleClick
{
   LV_GetText(FileCode,A_EventInfo, 5) ; Get the text of field 5
   LV_GetText(FileDir, A_EventInfo, 2) ; Get the text of field 2
   Run %FileDir%\%FileCode%,, UseErrorLevel
   If ErrorLevel
      MsgBox Could not open "%FileDir%\%FileCode%".
}
Return

GuiContextMenu:                        ; At right-click or press of the Apps key
   If A_GuiControl <> MyListView       ; Display the menu only for clicks inside the ListView
      Return
   Menu MyContextMenu, Show, %A_GuiX%, %A_GuiY%
Return

ContextOpenFile:                       ; "Open" is selected in the context menu
ContextProperties:                     ; "Properties" is selected in the context menu
   FocusedRowNumber := LV_GetNext(0, "F")    ; Find the focused row
   If not FocusedRowNumber                   ; No row is focused
      Return
   LV_GetText(FileDir, FocusedRowNumber, 2)  ; Get the text of the second field
   LV_GetText(FileCode,FocusedRowNumber, 5)  ; Get the text of the first field
   IfInString A_ThisMenuItem, Open           ; User selected "Open" from the context menu
      Run %FileDir%\%FileCode%,, UseErrorLevel
   Else                                      ; User selected "Properties" from the context menu
      Run Properties "%FileDir%\%FileCode%",, UseErrorLevel
   If ErrorLevel
      MsgBox Could not perform requested action on "%FileDir%\%FileCode%".
Return

ContextClearRows:                      ; The user selected "Clear" in the context menu
   RowNumber = 0                       ; The first iteration to start the search at the top
   Loop {
      RowNumber := LV_GetNext(RowNumber - 1)
      If not RowNumber                 ; No more selected rows
         break
      LV_Delete(RowNumber)             ; Clear the row from the ListView
   }
Return

GuiSize:                               ; User resizing window
   If A_EventInfo = 1                  ; No action when window is minimized
      Return
   GuiControl Move, MyListView, % "W" (A_GuiWidth - 20) " H" (A_GuiHeight - 40)
Return

GuiClose:                              ; When the window is closed, exit the script automatically
ExitApp
The backup directory should be a compressed Windows directory to trade storage space to access time.

It is version 0.1, so there could be some bugs. Please report them! Also, extend the functionality:
- Error checking
- Multiple include patterns with wildcards (e.g. My Documents\*.doc, D:\Projects\*.ahk)
- Mark the directories which are to be processed recursively with a leading +. Without it only the top level is processed.
- Exclude patterns with wildcards (e.g. ~W*.tmp, $??$.t*)
- Filter restore files with patterns, time ranges…

Edit 20061020:
- Handle restore to non-existent directories
- Improved icons

murray654
  • Members
  • 4 posts
  • Last active: Oct 19 2006 04:35 PM
  • Joined: 19 Oct 2006
Hi

I thought I could use this script for backup / restore of our software.

I have run into a problem; the restore does not work

When I load the backup folder using the restore script, under the "file name" heading, the names of several files (.exe .jpg .bmp .dll) are replaced with c:\windows\system32\shell.dll. The correct backup file name appears under the heading "code"

... it seems as if the action to retrieve icons fails too, as all the icons are the same blank icon

Restore of these items (named ...shell32.dll in the restore dialog) fails. :-(

So far we have never needed to restore a backup, but one day maybe we will have to :-) I suppose I can manually copy and rename each item, but this is tedious... there are 61 out of 228 items affected

I am using the script as written, only modified the ini file as follows:

[Defaults]
SourceDirectory = C:\IBservers\the-host.net
BackupDirectory = H:\backup
Max_Copies = 3
Include = *.*
Exclude = temp., .tmp, ~$*.*

H: is a network drive.
Local computer is XP Pro with AVG Network version; peer to peer network
Remote computer is XP Pro with Norton Internet Security 2006

Any idea why this is happening?
Kind regards,

Murray

murray654
  • Members
  • 4 posts
  • Last active: Oct 19 2006 04:35 PM
  • Joined: 19 Oct 2006
:? I really wonder what is going on, because now I can restore all the files, all my icons are ok, and so are the file names.

It seems that the restore script requires the original files to exist. Which kind of defeats the object. What if a file is accidentaly erased, and you need to restore it from backup. This is what I tried to do in my test:
First I backed up a folder. This worked 100%.
Next I deleted the folder.
Then I tried to restore using the script
All .exe .bat .bmp .jpg and .dll file names were replaced with c:\windows\system32\shell32.dll
I could only restore if i did not select any exe bat bmp jpg or dll files and only if the original folder existedThe restore script was only refusing to restore .bmp .jpg .exe and .dll files. This is my best guess: I assume that these files contain their own icons, and if the original does not exist, the icon supplied by windows is from shell32.dll and this somehow messed up the restore file name. It also refused to recover a file if the original folder did not exist.

I have now used the code from the first example to create an alternative restore method, which works even if the original file does not exist. I just added some lines to read the backup folder location from the ini file, so the user does not need to go looking for it.
Kind regards,

Murray

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Give me a few hours, I will look into it.

murray654
  • Members
  • 4 posts
  • Last active: Oct 19 2006 04:35 PM
  • Joined: 19 Oct 2006
thanks, It is closing time here in South Africa, I will check the discussion in the morning +/- 14 hours from now :-)
Kind regards,

Murray

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
To create the path (if it isn't existing) the simple restore has to be extended to this
StringReplace file, A_LoopField, ``, :\ 

   StringReplace file, file, ``, \, All 

   ToolTip Restoring %path%\%A_LoopField%`nTo %file%

   SplitPath, file, , OutDir 

   FileCreateDir, %OutDir%

   FileCopy %path%\%A_LoopField%, %file%, 1 

*Not tested*
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks, I did it. Everything works now, except the icons.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I updated the original post:
- Handle restore to non-existent directories
- Improved icons

The scripts need further testing. Please report bugs and omissions.

murray654
  • Members
  • 4 posts
  • Last active: Oct 19 2006 04:35 PM
  • Joined: 19 Oct 2006
Thanks, it works as expected. I can't thank you enough.

Have a great weekend :-)
Kind regards,

Murray

lotamoka
  • Members
  • 1 posts
  • Last active: Feb 26 2008 09:29 PM
  • Joined: 26 Feb 2008
It did not work if copy and save it as backup.cmd. Any idea what i am missing ?

TheIrishThug
  • Members
  • 419 posts
  • Last active: Jan 18 2012 02:51 PM
  • Joined: 19 Mar 2006
This is an AutoHotkey script, it needs to be saved as .ahk and AutoHotkey must be installed on your computer.