Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / operating-systems / Windows

Common application data, virtualization, and access control lists

4.50/5 (4 votes)
24 Jan 2008CPOL3 min read 1   260  
Vista: what to do when storing common data is needed?

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)