It has been a while since the last time I blogged. The theme of my work for the last 4 months or so is really migration, migration, and migration!
- First I helped our team with a VB6 to VB.NET migration project that ran into some sticky interop problems.
- Secondly I worked as an advisor for a customer who was in the process of migrating their apps from Win2K to Vista. Please don't ask me why they chose to go to Vista while Win7 was coming out - it was a decision already rationalized by the management when I was brought in.
- And lastly, just a couple of weeks ago, I helped Microsoft deliver a Win7 training course for a customer who is looking to migrate to Win7 in the next 6 months. Although it is not unusual that I present in a conference room or mentor developers privately, this was actually my first time to do a formal training. The sessions were recorded and are being made available for later reuse by the customer. Speaking of the sessions on tape, I actually saw Oliver Stone and Michael Douglas when they were shooting Wall Street 2 on site at the Borders book store at the corner of Wall Street and Broadway. And that was when I was working on the Vista migration mentioned above.
Enough with the chronology and my celebrity sightings. As the title suggests, I'd like to cover the most common compatibility issues for Vista/Win7from a developer perspective. So here are the two top issues I have seen so far:
- Writing to %ProgramFiles% (e.g. C:\Program Files) and %SystemRoot% (e.g. C:\Windows) locations
- Writing to registry hive: HKLM\Software
It is quite common that legacy apps write configuration data to the above locations. But with UAC enabled in Vista/Win7, an admin user will be running an app with standard user privileges unless the app has a manifest explicitly requesting admin privilege or the app is started with "Run as administrator" context menu; And since standard user will only have read/execute rights to those locations, the attempt made by those apps to write configuration data to those locations will fail. Using the icacls at the command prompt shows the ACL of %ProgramFiles% as in the following on my machine:
C:\Program Files>icacls .
. NT SERVICE\TrustedInstaller:(F)
NT SERVICE\TrustedInstaller:(CI)(IO)(F)
NT AUTHORITY\SYSTEM:(M)
NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
BUILTIN\Administrators:(M)
BUILTIN\Administrators:(OI)(CI)(IO)(F)
BUILTIN\Users:(RX)
BUILTIN\Users:(OI)(CI)(IO)(GR,GE)
CREATOR OWNER:(OI)(CI)(IO)(F)
There is an additional twist here. The app may not be failing but you will not see files or registry keys written to the intended location. This is the work of data redirection(or UAC virtualization). The details can be found here. But basically:
- Writing to %ProgramFiles%\MyApp\myapp.ini gets redirected to C:\Users\Username\AppData\Local\VirtualStore\Program Files\MyApp\myapp.ini
- Writing to HKLM\Software\MyApp\ gets redirected to HKCU\Software\Classes\VirtualStore\MACHINE\Software\MyApp
When virtualization happens, you can fire up Process Monitor and notice a REPARSE event, followed by writing to those redirected locations.
As the redirection is on per-user basis, this can mess up the intended behavior of the application. For example, the application may be intended to be used by multiple users on one machine and the configuration data should be shared. Microsoft is making no guarantee that the virtualization will be in future Windows. So what to do as a developer? The best practice for writing per machine configuration data is using %ProgramData% (e.g. C:\ProgramData). For per user configuration data, use %LocalAppData% (e.g. C:\Users\User\AppData\Local) for local or %AppData% for roaming user profile.
Running icacls on %ProgramData% shows that a standard user would have additional AD(append data/add subdirectory), WD(write data/add file), WEA(write extended attributes) and WA(write attributes) privileges:
C:\ProgramData>icacls .
. NT AUTHORITY\SYSTEM:(OI)(CI)(F)
BUILTIN\Administrators:(OI)(CI)(F)
CREATOR OWNER:(OI)(CI)(IO)(F)
BUILTIN\Users:(OI)(CI)(RX)
BUILTIN\Users:(CI)(WD,AD,WEA,WA)
And lastly, don't hard code any of the paths we have talked about. There are well-known APIs to get them. In .NET, follow this sample:
string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
And if you write C++ code, here is a sample:
void ShowFolders(HWND hWnd)
{
LPWSTR wszPath = NULL;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath);
if ( SUCCEEDED(hr) )
{
// work with wszPath
CoTaskMemFree(wszPath);
}
hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath);
if ( SUCCEEDED(hr) )
{
// work with wszPath
CoTaskMemFree(wszPath);
}
hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath);
if ( SUCCEEDED(hr) )
{
// work with wszPath
CoTaskMemFree(wszPath);
}
}