通用应用程序数据、虚拟化和访问控制列表






4.50/5 (4投票s)
Vista:
引言
在早期版本的 Windows 中,当应用程序需要写入访问某个位置(例如系统目录或受保护的注册表项)时,除非用户明确拥有该文件的写入访问权限或具有管理员权限,否则应用程序可能会失败。Vista 通过文件和注册表虚拟化解决了这个问题:操作系统会将文件/注册表项放置在用户配置文件中的一个虚拟位置,以便应用程序可以写入文件/注册表项而不会失败。之后,当应用程序再次读取此文件/注册表项时,系统将提供虚拟存储中的文件。
虚拟化会带来一个不便:因为文件/注册表项存储在保存它的用户的虚拟位置,其他用户将无法看到它!那么,当需要存储通用数据时该怎么办?
一种可能的解决方案是通过使所有用户都能读写数据来避免虚拟化。因此,需要更改应用程序数据文件夹或注册表项的访问控制列表 (ACL) 以允许读写访问。
本文介绍了一种在应用程序安装过程中更改应用程序文件夹或注册表项 ACL 的方法。
文件夹
作为所有用户共享的应用程序特定数据的通用存储库的目录是 CommonApplicationData
,其位置取决于操作系统。
- 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
因此,安装应用程序必须获取 CommonApplicationData
的位置,在那里它将创建一个新的通用数据文件夹并向所有用户添加权限。
我们使用 Nullsoft Scriptable Install System (NSIS) 开发的工具 appdatatool.exe 检索 CommonApplicationData
的位置,在那里它创建一个文件夹,文件夹名称从命令行获取。然后,它允许所有用户读取、写入和删除访问。
要检索 CommonApplicationData
的路径,我们使用 API 函数 SHGetFolderPath
,该函数包含在版本 5 及更高版本 (Windows 2000 及更高版本) 的 SHFolder.dll 中。如果您运行的是早期版本,则 SHGetFolderPath
函数要求您重新分发 SHFolder.dll 文件。该文件是可自由重新分发的,并且在最新的 Platform Software Development Kit (SDK) 中,您可以获得应与早期操作系统一起使用的 ShFolder.Exe 工具。
然后,在典型的应用程序安装中,添加了两项操作:
- 在 Windows 95、98 或 NT 上运行时,静默执行 SHFolder.exe。
- 静默执行 appdatatool.exe;通用文件夹的名称作为参数从命令行传递。
这两项操作将在安装结束时按此顺序执行。
这是 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
注册表项
作为所有用户共享的应用程序特定数据的通用存储库的注册表项是 HKEY_LOCAL_MACHINE\Software。
我们使用 NSIS 开发的工具 regappdatatool.exe 在 HKEY_LOCAL_MACHINE\Software 中创建和初始化一个注册表项。该项的名称、字符串值和初始化值从命令行检索。然后,该工具允许所有用户读取、写入和删除访问。
然后,在典型的应用程序安装中,添加了以下操作:
- 静默执行 regappdatatool.exe;从命令行传递的参数(格式如下:“string1” “string2” ... “stringN”)是:
- 通用注册表项的名称
- 字符串值
- 字符串初始化
;
; 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
结论
在所有 MSDN 文档中,都没有关于如何解决这些问题的指南或操作说明。特别是,共享数据库或日志文件供所有用户使用的应用程序,或共享注册表项以进行设置的应用程序,都会遇到这个问题……而这些只是最常见的(也许是糟糕的)行为。通过本文,我们希望为在 Vista 时代仍需要使用老式应用程序的各位提供一个快速解决方案。
注意
请注意 AccessControl.dll NSIS 插件的版本:请使用 2008 年 1 月 7 日或之后版本。
参考文献
历史
- 24/01/08 - 首次发布。