The first major obstacle to testing an application for compatibility on Windows Vista is to actually get that software installed, and there are still some challenges remaining in achieving this. One of the issues that has popped up is with deferred custom actions.
When you specify the type of a custom action in the MSI database, you are creating a bitmask. This isn't always easy to see, since the Orca MSI editor provided with the Platform SDK presents this information in decimal rather than hexadecimal format, but it is true. To specify a deferred custom action, you specify msidbCustomActionTypeInScript, or 0x400, in the bitmask. So, if you wanted to do this with a DLL, you would logical or msidbCustomActionTypeInScript and msidbCustomActionTypeDll == 0x400 | 0x1 == 0x401 == 1025.
This is one of the custom action types that we are seeing that are causing problems as a result of UAC. With nothing more than 0x401, this cutom action will be run impersonating the logged in user, not in the system context. It is not using the elevated token, it is using the standard token, which has quite a few privileges and groups removed from it, including the local Administrators group! If your custom action requires elevated permissions to run, then it will fail, as will your installation.
However, you can specify that the custom acton will not use impersonation. By including in the bitmask msidbCustomActionNoImpersonate == 0x800, you will no longer be impersonating the currently logged in user (with their non-elevated token), and instead be running with system privileges just like the rest of the MSI installation. This avoids the issue of an access denied message being triggered by some action in the MSI (at the expense of removing the user impersonation). In the above example, we would use msidbCustomActionTypeInScript | msidbCustomActionTypeDll | msidbCustomActionNoImpersonate == 0x400 | 0x1 | 0x800 == 0xC01 == 3073.
For the setup developer, this means that custom action types that require impersonation of the user should be separated from custom action types that require elevated permissions - the impersonated user no longer has these permissions by default and this is no longer an appropriate assumption. For the administrator, it makes it possible to modify the custom action type (MSI is white box, and you can edit the contents of the MSI database using the Orca SDK tool) to hopefully end up with a successful setup.
I ran into this issue working with a hardware manufacturer working on updating their drivers for use on Windows Vista. You may already know that you can uninstall a device directly within the Device Manager. You can simply right click and select uninstall. The drivers are then removed from the system.
This customer obviously wanted to uninstall the driver each time they had a new build to test. However, since it was a USB device, they would uninstall the driver, and then Plug and Play would immediately detect the device again and re-install the driver. If they removed the USB device, the device would disappear from Device Manager, and they could no longer remove it. They were having to to into the registry each time to remove the entry for their device.
You can configure the system to show you devices that you have installed, but that are not currently installed in the system. This would allow you to uninstall that device, which is helpful if you are, say, developing drivers. Or maybe you're just nostalgic and want to see all of the hardware that you have attached to your system.
You can create a new environment variable DEVMGR_SHOW_NONPRESENT_DEVICES and set the value to 1. Then, within Device Manager, you select View -> Show Hidden Devices. Now you can see everything, and you can uninstall something that is no longer there.
Windows has included securable objects for quite some time now, but Windows Vista adds a new concept: Mandatory Integrity Control. You can read a detailed description here; in essence, in addition to requiring a particular SID to access a secured resource, you must be running with a token containing the required integrity level. When you are running as a standard user, your token contains an ACE called Mandatory Label\Medium Mandatory Level. When you are running elevated, your token contains an ACE called Mandatory Label\High Mandatory Level. Internet Explorer Protected Mode runs the process with a Low Mandatory Level.
The obvious question is this: how do I set the mandatory level for a securable object?
Within code, you can use the AddMandatoryAce API to add a mandatory level to a securable object.
You can also modify the mandatory level reqirements without writing code using the icacls utility. It contains a command line switch: /setintegritylevel. For example, if I wanted to change the integrity level on a folder from medium (the default) to low, I could use "icacls c:\myLowMicFolder /setintegritylevel l". Note that you cannot change the integrity level to high from a medium IL process, so if you wanted to change the value to high, you would need to do so from a high IL (elevated) command prompt.
One new feature in Windows Vista that makes a number of visual effects possible is the Desktop Window Manager. The DWM works through composition. Rather than rendering directly to the screen, applications render to their own off-screen bitmap. The DWM then takes all of these bitmaps and composites them to render to the actual screen. Because there is always a current bitmap available, the DWM can start to use this bitmap for other things. It can render it when you mouse over the entry in the task bar. It can animate the shrinking without each individual application having to implement this. It can be rendered in Alt-Tab. It can be rendered in 3D in Windows-Tab. If the application stops responding, it can still render the last known state instead of a white screen because the bitmap is independent - it doesn't matter if another window covers it up. (Windows will, however, "frost" it to make it clear that the window isn't really prepared to do anything useful.)
This behavior means a couple of things for existing applications. One example: you used to be able to assume that you were rendering directly to the screen. This assumption could lead you to an implementation detail that no longer works as well on Windows Vista. For example, if you assume that not rendering a pixel is the equivalent to rendering a transparent pixel, that is no longer true. Now, the compositor could very well render that pixel as black when it composites it, and I have seen that on a few applications so far.
Here is an example of an application that simply draws a star in the client area of a dialog box. It works fine without desktop composition, but with desktop composition enabled renders with a black box around the star:
#include <windows.h>#include "resource.h"
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);}
BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) { case WM_INITDIALOG: return TRUE; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hDlg, &ps); RECT rc; GetClientRect(hDlg, &rc); SetMapMode(hdc, MM_ANISOTROPIC); SetWindowExtEx(hdc, 100, 100, NULL); SetViewportExtEx(hdc, rc.right, rc.bottom, NULL); POINT aptStar[6] = {50,2, 2,98, 98,33, 2,33, 98,98, 50,2}; SelectObject(hdc, GetStockObject(DC_PEN)); SetDCPenColor(hdc, RGB(0x4C, 0x4C, 0x4C)); SelectObject(hdc, GetStockObject(DC_BRUSH)); SetDCBrushColor(hdc, RGB(0x13, 0x70, 0xAB)); Polygon(hdc, aptStar, 6); EndPaint(hDlg, &ps); return TRUE; } case WM_ERASEBKGND: return TRUE; case WM_LBUTTONDOWN: EndDialog(hDlg, TRUE); return TRUE; default: return FALSE; }}
If you wanted to custom draw something with transparency in a composited world, you actually have to render the transparent pixels explicitly.
Of course, I am somewhat unnerved by posting code that is explicitly and intentionally wrong. However, in this particular example I really couldn't think of a way to explain the outcome without giving you the code to actually see the results both with or without the DWM enabled. In a nutshell, you no longer want to make the assumption that you are rendering directly to the screen. If you assume that you own a particular region of the screen, and render everything in that region, you will be safe whether or not what you render goes directly to the screen or goes to an off-screen bitmap.
User Account Control (UAC) on Windows Vista changes the paradigm of being an administrator on a Microsoft Windows operating system. Rather than wielding full administrative privileges all of the time, the token is "split" and there are two of them. If you run an application normally, it is given the token that has fewer privileges (a "standard user" token, if you will, although the Administrators group is still present and set to "deny only" so securable objects that an administrator is explicitly forbidden from accessing will still be denied to this user). If you create a process elevated, you are prompted to approve the elevation, after which the process is provided with an "unfiltered" token that grants this application full administrator credentials.
This is a huge win for security. However, it does break some of the paradigms that you may be used to using when developing applications. One example is checking to see if the user is an administrator explicitly. Most of the tools that help identify more LUA bugs (and these tools are becoming pretty indispensible now that pretty much everybody is running as a standard user the majority of the time) will flag this as a potential LUA bug. You see, this is something that can be done for good (you are checking to see if the user is an administrator to determine whether or not you want to offer them the option of launching another process elevated to provide additional functionality), and it is something that can be done for evil (you are checking to see if the user is an administrator, because it is easier just to fail than to fix the LUA bug). For now, let's assume that you are using this power for good.
If you happen to be using the handy shell32 API IsUserAnAdmin, you will find that it will return true if the process is elevated, and false if it is not. Note that the Boolean return value doesn't provide you with any information that will help you determine if the user CAN elevate - it just tells you if you already have. What can you do if you want to know if the user CAN elevate, whether or not they already have?
The GetTokenInformation API provides a new ability to return a TokenElevationType structure. As of Windows Vista RC1, I do not see this documented in the Windows SDK, but you can find it in the Windows header files (winbase.h and winnt.h). So, I whipped together a little sample that you can run from the command line to determine not only whether the current process is elevated, but also whether the user happens to be a member of the administrators group:
#include <windows.h> #include <stdio.h>
int main() { HANDLE hToken; TOKEN_ELEVATION_TYPE elevationType; DWORD dwSize;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken); GetTokenInformation(hToken, TokenElevationType, &elevationType, sizeof(elevationType), &dwSize);
switch (elevationType) { case TokenElevationTypeDefault: wprintf(TEXT("\nTokenElevationTypeDefault - User is not using a split token.\n")); break; case TokenElevationTypeFull: wprintf(TEXT("\nTokenElevationTypeFull - User has a split token, and the process is running elevated.\n")); break; case TokenElevationTypeLimited: wprintf(TEXT("\nTokenElevationTypeLimited - User has a split token, but the process is not running elevated.\n")); break; }
if (hToken) { CloseHandle(hToken); } }
As you can see, it's fairly straightforward. (I have elided error handling and return value checking for clarity.) If you are looking to use this information for good, I hope this helps. If you are looking to use this information for evil, I hope you don't find this post!
Update: Note that this technique detects if the token is split or not. In the vast majority of situations, this will determine whether the user is running as an administrator. However, there are other user types with advanced permissions which may generate a split token during an interactive login (for example, the Network Configuration Operators group). If you are using one of these advanced permission groups, this technique will determine the elevation type, and not the presence (or absence) of the administrator credentials.
Updated 03-March-2010
Apparently my previous update didn’t scare enough people away from this approach, so here goes: be very afraid of this approach. It will correctly tell you if you have a split token, but that doesn’t necessarily mean anything useful. For example, what happens if you disable UAC? You won’t have a split token. You’d get TokenElevationTypeDefault. What happens if you are logged in as the .\Administrators account? Same thing. Neither one means you’re a standard user, which is a common mistake by misapplying the above logic for the “typical” case. What about if you happen to have one, and only one, super privilege, and you elevated to get that into your token? Then you’d have TokenElevationTypeFull – which is frequently interpreted as meaning you’re an admin.
The *right* thing to do is not care. If you think you need admin rights, then manifest your binary, and ask for them. Does the current user’s capabilities matter? If they have a second admin account, then why does it matter if their current one is a standard user?
And you certainly don’t want to go looking for the Administrators ACE in the token in order to authorize something. Because, you know, it has a flag that says “Deny Only” on it. If you go granting access, then that’s directly violating the “Deny Only” intent. Kind of like saying, “oh, I see you’re on the no gambling list – come on into the casino…”
In other words, despite the fact that I wrote this (back in my youth), unless you know precisely what it is doing, please don’t use this. Because it probably doesn’t do what you think it does.