Introduction
In previous versions of Windows, when an application requires the ability to write to access a location (such as system directories or protected Registry keys), unless the user explicitly has write access to that file or has administrative rights, the application may fail. Vista solves the problem through file and Registry virtualization: the OS will place the file/Registry key into a virtual location in the user’s profile so that the application will write to the file/Registry key without failing. Later, when the application reads back this file/Registry key, the system will provide the one in the Virtual Store.
Virtualization involves an inconvenience: because the file/Registry key is stored into the virtual location of the user who saved it, other users won’t be able to see it! So, what to do when storing common data is required?
A possible solution is to avoid virtualization by making data accessible for all users for read and write. So, the Access Control List (ACL) for the application’s data folder or the Registry key has to be changed to allow read/write access.
This article describes a way of changing the ACL for an application’s folder or Registry key during the installation of that application.
Folder
The directory that serves as a common repository for application-specific data shared by all users is CommonApplicationData
, whose locations depend on the OS:
- Windows XP - %systemdrive%\Documents and Settings\All Users\Application Data
- Windows Vista - %systemdrive%\ProgramData
- Windows 2000 - %systemdrive%\Documents and Settings\All Users\Application Data
- Windows Server 2003 - %systemdrive%\Documents and Settings\All Users\Application Data
- Windows 98 - %systemdrive%\Windows\All Users\ Application Data
So, the installation application must get the location of CommonApplicationData
, where it will create a new folder for common data and add permissions to all users.
Our tool appdatatool.exe, developed with the Nullsoft Scriptable Install System (NSIS), retrieves the location of CommonApplicationData
, where it creates a folder, getting its name from the command line. Then, it allows read, write, and delete access for all users.
To retrieve the path of CommonApplicationData
, we use the API function SHGetFolderPath
, included in SHFolder.dll from version 5 onwards (Windows 2000 and the following OSs). If you are running a previous version, then the SHGetFolderPath
function requires that you redistribute the SHFolder.dll file. The file is freely redistributable, and in the latest Platform Software Development Kit (SDK), you can obtain the ShFolder.Exe tool that should be used with the previous OSs.
Then, to the typical installation of the application, two operations have been added:
- silent execution of SHFolder.exe, if the installation is running on Windows 95, 98, or NT.
- silent execution of appdatatool.exe; the name of the common folder is passed as the parameter from the command line.
Both operations are executed at the end of installation, in this order.
Here is the code for appdatatool.exe:
;
; appdatatool.nsi
;
;include for InstallLib
!include Library.nsh
!include MUI.nsh
!include nsDialogs.nsh
;--------------------------------
; The name of the installer
!define PCK_NAME "appdatatool"
!define VERSION 1.0.0
!define YEAR 2007
Name "appdatatool"
!define FILE_DESC "Installation tool ${PCK_NAME}"
!define INSTALL_REGKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PCK_NAME}"
!define COMPANY "Michela Carriero - Lyra"
!define URL
; The file to write
OutFile "${PCK_NAME}.exe"
SilentInstall silent
; The default installation directory
InstallDir "$PROGRAMFILES\${COMPANY}\${PCK_NAME}"
;--------------------------------
; MUI Settings / Icons
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico"
;--------------------------------
;Pages
!insertmacro MUI_PAGE_INSTFILES
;--------------------------------
CRCCheck on
XPStyle on
ShowInstDetails hide
VIProductVersion "${VERSION}.0"
VIAddVersionKey /LANG=${LANG_ITALIAN} ProductName "${PCK_NAME}"
VIAddVersionKey /LANG=${LANG_ITALIAN} ProductVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ITALIAN} CompanyName "${COMPANY}"
VIAddVersionKey /LANG=${LANG_ITALIAN} CompanyWebsite "${URL}"
VIAddVersionKey /LANG=${LANG_ITALIAN} FileVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ITALIAN} FileDescription "${FILE_DESC}"
VIAddVersionKey /LANG=${LANG_ITALIAN} LegalCopyright ""
;--------------------------------
VAR APPLICATION_COMMON_FOLDER
; The stuff to install
Section "Appdatatool (compulsory)"
SectionIn RO
Call GetParameters
Pop $0
; Retrieving CommonApplicationData path (in $1)
System::Call "shfolder::SHGetFolderPath(i $HWNDPARENT, i 0x0023, i 0, i 0, t.r1)"
StrCpy $APPLICATION_COMMON_FOLDER "$1\$0"
CreateDirectory $APPLICATION_COMMON_FOLDER
# Make the directory "$APPLICATION_COMMON_FOLDER\application name"
# read write delete accessible by all users
; SID instead of BU as users (it works also on Windows 2000)
AccessControl::GrantOnFile \
"$APPLICATION_COMMON_FOLDER" "(S-1-5-32-545)"
"GenericRead + GenericWrite + Delete"
SectionEnd
; GetParameters
; input, none
; output, top of stack (replaces, with e.g. whatever)
; modifies no other variables.
Function GetParameters
Push $R0
Push $R1
Push $R2
Push $R3
StrCpy $R2 1
StrLen $R3 $CMDLINE
;Check for quote or space
StrCpy $R0 $CMDLINE $R2
StrCmp $R0 '"' 0 +3
StrCpy $R1 '"'
Goto loop
StrCpy $R1 " "
loop:
IntOp $R2 $R2 + 1
StrCpy $R0 $CMDLINE 1 $R2
StrCmp $R0 $R1 get
StrCmp $R2 $R3 get
Goto loop
get:
IntOp $R2 $R2 + 1
StrCpy $R0 $CMDLINE 1 $R2
StrCmp $R0 " " get
StrCpy $R0 $CMDLINE "" $R2
Pop $R3
Pop $R2
Pop $R1
Exch $R0
FunctionEnd
Registry key
The Registry key that serves as a common repository for application-specific data shared by all users is HKEY_LOCAL_MACHINE\Software.
Our tool regappdatatool.exe, developed with NSIS, creates and initializes a Registry key in HKEY_LOCAL_MACHINE\Software. The name of the key, the string value, and the initialization value are retrieved from the command line. Then, the tool allows read, write, and delete access for all users.
Then, to the typical installation of the application, these operations have been added:
- silent execution of regappdatatool.exe; the parameters passed from the command line (in this format: "string1" "string2" ... "stringN") are:
- name of the common Registry key
- string value
- string initialization
;
; regappdatatool.nsi
;
;include for InstallLib
!include Library.nsh
!include MUI.nsh
!include nsDialogs.nsh
;--------------------------------
; The name of the installer
!define PCK_NAME "regappdatatool"
!define VERSION 1.0.0
!define YEAR 2007
Name "regappdatatool"
!define FILE_DESC "Installation tool ${PCK_NAME}"
!define INSTALL_REGKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PCK_NAME}"
!define COMPANY "Michela Carriero - Lyra"
!define URL
; The file to write
OutFile "${PCK_NAME}.exe"
SilentInstall silent
; The default installation directory
InstallDir "$PROGRAMFILES\${COMPANY}\${PCK_NAME}"
;--------------------------------
; MUI Settings / Icons
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico"
;--------------------------------
;Pages
!insertmacro MUI_PAGE_INSTFILES
;--------------------------------
; split di stringhe che sono nel formato "string1" "string2"..."stringN"
; split substrings in this format: "string1" "string2"..."stringN"
!macro GET_STRING_TOKEN INPUT PART
Push $R0
Push $R1
Push $R2
; R0 = indice di scorrimento stringa
; R0 = index of current position in the string
StrCpy $R0 -1
; R1 = indice del carattere " da trovare
; R1 = index of '"' character to be found
IntOp $R1 ${PART} * 2
IntOp $R1 $R1 - 1
findStart_loop_${PART}: ; cerco il " che indica l'inizio
; della sottostringa di interesse
; searching '"' character beginning the substring
IntOp $R0 $R0 + 1 ; i++
StrCpy $R2 ${INPUT} 1 $R0 ; getting next character
; user_var(destination) str [maxlen] [start_offset]
StrCmp $R2 "" error_${PART}
StrCmp $R2 '"' 0 findStart_loop_${PART}
IntOp $R1 $R1 - 1
IntCmp $R1 0 0 0 findStart_loop_${PART}
; val1 val2 jump_if_equal [jump_if_val1_less] [jump_if_val1_more]
; salvo in R1 l'indice di inizio della sottostringa di interesse
; storing in R1 the index beginning the substring
IntOp $R1 $R0 + 1
findEnd_loop_${PART}: ; cerco il " successivo, che indica
; la fine della stringa di interesse
; searching '"' character ending the substring
IntOp $R0 $R0 + 1 ; i++
StrCpy $R2 ${INPUT} 1 $R0 ; getting next character
; user_var(destination) str [maxlen] [start_offset]
StrCmp $R2 "" error_${PART}
StrCmp $R2 '"' 0 findEnd_loop_${PART}
; R0 = indice di fine della sottostringa di interesse
; R0 = the index ending the substring
IntOp $R0 $R0 - $R1 ; salvo in R0 la lunghezza della sottostringa di interesse
; storing in R0 the substring's length
StrCpy $R0 ${INPUT} $R0 $R1
Goto done_${PART}
error_${PART}:
StrCpy $R0 error
done_${PART}:
Pop $R2
Pop $R1
Exch $R0
!macroend
;--------------------------------
CRCCheck on
XPStyle on
ShowInstDetails hide
VIProductVersion "${VERSION}.0"
VIAddVersionKey /LANG=${LANG_ITALIAN} ProductName "${PCK_NAME}"
VIAddVersionKey /LANG=${LANG_ITALIAN} ProductVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ITALIAN} CompanyName "${COMPANY}"
VIAddVersionKey /LANG=${LANG_ITALIAN} CompanyWebsite "${URL}"
VIAddVersionKey /LANG=${LANG_ITALIAN} FileVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ITALIAN} FileDescription "${FILE_DESC}"
VIAddVersionKey /LANG=${LANG_ITALIAN} LegalCopyright "Copyright(c) ${YEAR} ${COMPANY}"
;--------------------------------
VAR REG_KEY
VAR REG_VALUE
VAR REG_DATA
; The stuff to install
Section "Regappdatatool (compulsory)"
SectionIn RO
Call GetParameters
Pop $0
; chiave
!insertmacro GET_STRING_TOKEN $0 1
Pop $REG_KEY
; valore
!insertmacro GET_STRING_TOKEN $0 2
Pop $REG_VALUE
; dato
!insertmacro GET_STRING_TOKEN $0 3
Pop $REG_DATA
WriteRegStr HKLM "Software\$REG_KEY" "$REG_VALUE" "$REG_DATA"
# Make "HKEY_LOCAL_MACHINE\Software\application name\$REG_KEY"
# fully accessible by all users
; SID instead of BU as users (it works also on Windows 2000)
; GrantOnRegKey [/NOINHERIT] <rootkey> <regkey> <trustee> <permissions>
AccessControl::GrantOnRegKey /NOINHERIT \
HKLM "Software\${COMPANY}\$REG_KEY" "(S-1-5-32-545)" "FullAccess"
SectionEnd
; GetParameters
; input, none
; output, top of stack (replaces, with e.g. whatever)
; modifies no other variables.
Function GetParameters
Push $R0
Push $R1
Push $R2
Push $R3
StrCpy $R2 1
StrLen $R3 $CMDLINE
;Check for quote or space
StrCpy $R0 $CMDLINE $R2
StrCmp $R0 '"' 0 +3
StrCpy $R1 '"'
Goto loop
StrCpy $R1 " "
loop:
IntOp $R2 $R2 + 1
StrCpy $R0 $CMDLINE 1 $R2
StrCmp $R0 $R1 get
StrCmp $R2 $R3 get
Goto loop
get:
IntOp $R2 $R2 + 1
StrCpy $R0 $CMDLINE 1 $R2
StrCmp $R0 " " get
StrCpy $R0 $CMDLINE "" $R2
Pop $R3
Pop $R2
Pop $R1
Exch $R0
FunctionEnd
Conclusion
In all MSDN, there’s no guideline or a howto that describes a solution for these issues. In particular, applications sharing a database or log files for all users, or sharing Registry keys for settings, suffer from this problem... and these are just the most common (maybe bad) behaviours. With this article, we hope to give a quick solution for all guys who still need to use the old fashioned applications in the Vista era.
NB
Be careful with the version of the AccessControl.dll NSIS plug-in: use the 7th January 2008 version, or later.
Bibliography
History
- 24/01/08 - First release.