Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

The Windows Access Control Model Part 4

, 7 Sep 2005
The final article in the access control series presents a guide to the access control editor and its associated ISecurityInformation interface.
filepermsbox-1_11.zip
filepermsbox111.zip
release
FilePermsBox.dll
FilePermsBox.exe
FilePermsBox.Interop.dll
filepermsboxsrc.zip
Frontend
FilePermsBox.exe.manifest
InnoForm.isf
SetupFpms.iss
NetWrapper
FilePermsBox.Interop.tlb
FPermBoxKey.snk
FilePermsBox.Interop.dll
FilePermsBoxDLL
165.avi
FilePermsDll.def
FilePermsDll.manifest
FilePermsDll64.def
/**
*	This code is by OShah. all code will have the following licence.
*	Copyright Shexec32. All code bears the following licence:
**/

/**
*	Copyright Shexec32 2004-2005. All rights reserved.
*
*	THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*	ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*	TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
*	PARTICULAR PURPOSE.
**/

#include"SecurityCore.h"
#include"ShellDLL.h"


int APIENTRY DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID)
{/**
*	This program is derived from QueryServiceConfig v1.00. Therefore there are many references to
*	QueryServiceConfig. If it wasn't for this paragraph, this header will be the same as seen in
*	QueryServiceConfig. You'll notice I forgot to update FilePermissionsBox.h.
*
*	In DllMain the only thing we need to do is initialize g_hInst and g_RefCount
*	(and call DisableThreadLibraryCalls).
**/
	switch(dwReason)
	{
		case DLL_PROCESS_ATTACH:
		{/* Entry: Initialise globals and store our handle in g_hInst. */
			g_hInst = hInst;
			g_RefCount = 0;

			/* Create the Mutex (Allows setup to detect if we are running). */
			g_hMutex = ::CreateMutex(NULL, FALSE, _T("FilePermsBox_ShellDLLActive"));
			if(g_hMutex == INVALID_HANDLE_VALUE || g_hMutex == NULL)
			{/* This mutex needs to be global, because if our file is active in any session, the file will be locked. */
				g_hMutex = ::CreateMutex(NULL, FALSE, _T("FilePermsBox_ShellDLLActive"));
			}
			::DisableThreadLibraryCalls(hInst);
			::SetLastError(ERROR_SUCCESS);

			break;
		}
		case DLL_PROCESS_DETACH:
		{/* Exit. */
			if(g_hMutex != NULL && g_hMutex != INVALID_HANDLE_VALUE)
				::CloseHandle(g_hMutex);
			g_hMutex = NULL;
			break;
		}
		default:
			break;

	}
	return TRUE;
}



STDAPI DllCanUnloadNow(void)
{/* The refcount should be 0 if it is ok to unload this DLL. */
	if(g_RefCount < 1)
		return S_OK;
	else return S_FALSE;
}



STDAPI DllRegisterServer(void)
{/* API Entry Point for FilePermsBoxDLL. This is our ticket into DllInstallEx(). */
	return DllInstall(TRUE, L"");
}



STDAPI DllUnregisterServer(void)
{/* API EntryPoint for FilePermsBoxDLL. This is our ticket into DllInstallEx(). */
	return DllInstall(FALSE, L"");
	/* No version checking here, because the user needs a way to uninstall it (if they somehow installed it on Win9x). */
}


STDAPI DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
{/** API EntryPoint for FilePermsBoxDLL. This is the Big Ticket into DllInstallEx(). This is the entrypoint used
*	by the frontend, and the only way to use the extra parameters of Options_Flags.
*	The result is returned by DllInstallEx(). See that for more information.
*	CommandLine parameters (put it in the pszCmdLine string):
*		/silent: enables the SILENT flag.
*		/allusers: enables the ALLUSERSONLY flag.
*		/meonly: enables the MEONLY flag.
*	Guess how to enable HKCRONLY flag.
**/
	__int64 Options_Flags = 0;
	/* HACKHACK:Win9x crashes if OLEAUT32 ain't loaded. */
	_bstr_t Win98Hack;

	if(::IsBadStringPtrW(pszCmdLine, 1))
	{/* we don't know the size of pszCmdLine. */
		return E_INVALIDARG;
	}
	std::basic_string<TCHAR> sCmdLine (pszCmdLine);

	if(bInstall == TRUE)
	{/* If the user wants to uninstall, allow it even if windows does not match the requirements. */
		if(CheckVersion() != TRUE)
			return E_NOTIMPL;
	}

	if( sCmdLine.find(_T("/silent")) < sCmdLine.size() )
	{/* silent flag */
		Options_Flags = Options_Flags | SILENT;
	}
	if( sCmdLine.find(_T("/allusers")) < sCmdLine.size() )
	{/* allusers flag */
		Options_Flags = Options_Flags | ALLUSERSONLY;
	}
	if( sCmdLine.find(_T("/meonly")) < sCmdLine.size() )
	{/* meonly flag */
		Options_Flags = Options_Flags | MEONLY;
	}

	return DllInstallEx(bInstall, Options_Flags);
}



HRESULT WINAPI DllInstallEx(BOOL bInstall, __int64 Options_Flags)
{/** This is The primary function that [un]registers the program. The other DllEntryPoints
*	(DllRegisterServer et al) are just thunks into this function. The function returns a regsvr32
*	code if a failure occurred, or S_OK.
*
*	bInstall has the same meaning as DllInstall's bInstall parameter (True: Register, False: Unregister).
*	The Options_Flags parameter is a set of flags that indicate how to install the application (think of it as the
*	pszCmdLine parameter of DllInstall, translated into a DWORD).
*
*	Currently supported options for Options_Flags:
*	SILENT: displays no UI (silently fail).
*	ALLUSERSONLY: Force Install for All Users (Use HKLM\SOFTWARE\Classes instead of HKCR).
*	MEONLY: Installs for this user only. (Use HKCU\SOFTWARE\Classes instead of HKCR).
*
*	By default, the function displays a suppressible MessageBox if a problem occurs, and writes to HKCR.
*	ALLUSERSONLY | MEONLY means HKCR only.
*
*	A Suppressible Message Box is a message box that's only shown if the SILENT flag is not present.
*
*	Here are some common results returned directly by FilePermsBox:
*	E_NOTIMPL: FilePermsBox determined that the OS is incompatible.
*	E_ACCESSDENIED: You do not have permission to alter the resource (Try without the MEONLY flag etc.).
*	FACILITY_WIN32 | <Any code>: this is an error returned by the OS.
*
*	If the we're being installed the function creates the following registry keys:
*	where @_GUID == __uuidof(FilePermsBox) == {009437af-9024-4572-8a1e-cfc5d55c8171}
*
*	[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved]
*	"@_GUID"="FilePermsBox Property Sheet"
*	; ALLUSERSONLY: If this fails, A Suppressible MessageBox is shown to the user.
*
*	[HKEY_CLASSES_ROOT\*\shellex\PropertySheetHandlers\FilePermsBox]
*	@="@_GUID"
*
*	[HKEY_CLASSES_ROOT\CLSID\@_GUID]
*	@="FilePermsBox Sheet"
*
*	[HKEY_CLASSES_ROOT\CLSID\@_GUID\InProcServer32]
*	@="FilePermsBox.dll"
*	ThreadingModel="Apartment"
*
*	[HKEY_CLASSES_ROOT\CLSID\@_GUID\ProgID]
*	@="FilePermsBox.SecurityBox.1"
*
*	[HKEY_CLASSES_ROOT\CLSID\@_GUID\VersionIndependentProgID]
*	@="FilePermsBox.SecurityBox"
*
*	[HKEY_CLASSES_ROOT\Directory\PropertySheetHandlers\FilePermsBox]
*	@="@_GUID"
*
*	[HKEY_CLASSES_ROOT\Drive\PropertySheetHandlers\FilePermsBox]
*	@="@_GUID"
*
*
*	And breathe and stop!
*	On Uninstall, these keys are deleted. FilePermsBox will attempt to remove all entries (even if there are failures).
*	If a problem occurs, the problem is logged, and the deletion continues. At the end, a supressible messagebox
*	is then shown to the user explaining the problem[s].
**/

	HRESULT dwErr = 0;
	/* Common Tasks. */
	int mBoxRes = 0;
	std::basic_stringstream<TCHAR> TError;
	registry_string RegKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"));
	registry_string hKey_ClassRoot (HKEY_CLASSES_ROOT, _T(""));

	std::basic_string<TCHAR> CLSID_IFilePermsBox_ToString;
	{/* Get our GUID in string form. */
		sized_array<TCHAR> StringGuid (39);
		/* GUIDs are only 39 characters long. */

		::StringFromGUID2(CLSID_IFilePermsBox, StringGuid.get(), 39);
		CLSID_IFilePermsBox_ToString = StringGuid.get();
	}

	if((Options_Flags & MEONLY) && (Options_Flags & ALLUSERSONLY))
	{/* Translate MEONLY And ALLUSERONLY into HKCRONLY */
		Options_Flags = Options_Flags &~ (MEONLY | ALLUSERSONLY);
		Options_Flags = Options_Flags | HKCRONLY;
	}

	if(bInstall == TRUE)
	{/* DllRegisterServer() */
		try {
			/* TODO: RegOverridePredefinedKey performs most of this functionality */
			if(Options_Flags & HKCRONLY)
			{/* Get HKCR */
				hKey_ClassRoot.InitPath(HKEY_CLASSES_ROOT, _T(""));
				if(!hKey_ClassRoot.exists(KEY_READ | KEY_WRITE) != TRUE)
				{/* Can't use this. throw out. */
					throw STD(_T("Could not access the key HKEY_CLASSES_ROOT"));
				}
			}
			else if(Options_Flags & ALLUSERSONLY)
			{/* Try it from HKLM instead. */
				hKey_ClassRoot.InitPath(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Classes"));
				if(!hKey_ClassRoot.create(KEY_READ | KEY_WRITE) != TRUE)
				{/* Can't use this. throw out. */
					throw STD(_T("Could not access the requested key HKEY_LOCAL_MACHINE"));
				}
			}
			else if(Options_Flags & MEONLY)
			{/* Just this user: HKCU */
				hKey_ClassRoot.InitPath(HKEY_CURRENT_USER, _T("SOFTWARE\\Classes"));
				if(!hKey_ClassRoot.create(KEY_READ | KEY_WRITE) != TRUE)
				{/* Can't use this. throw out. */
					throw STD(_T("Could not access the requested key HKEY_CURRENT_USER"));
				}
			}
			else if(hKey_ClassRoot.exists(KEY_READ | KEY_WRITE) != TRUE)
			{/* Classic behaviour. Try HKCR then HKLM then fallback to HKCU */
				hKey_ClassRoot.InitPath(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Classes"));
				if( !hKey_ClassRoot.create(KEY_READ | KEY_WRITE))
				{/* Well install it for the user instead. */
					mBoxRes = SuppressibleMsgBox(Options_Flags, NULL,
						_T("There was an error installing this app for all users.\n")
						_T("Would you like to install it for yourself only?"), _T("Error Writing Registry"),
						MB_YESNO | MB_DEFBUTTON1);
					if(mBoxRes != IDYES) return E_ACCESSDENIED;
					hKey_ClassRoot.InitPath(HKEY_CURRENT_USER, _T("SOFTWARE\\Classes"));
				}
			}
			/* Here, we've succeeded getting a handle to HKCR. */
		} catch(const std::basic_string<TCHAR> &) {/* Here we haven't */
			mBoxRes = ::SuppressibleMsgBox(Options_Flags, NULL,
				_T("There was an error installing this app for all users.\n")
				_T("Would you like to install it for yourself only?"), _T("Error Writing Registry"),
				MB_YESNO | MB_DEFBUTTON2);
			/* We cannot fulfill the caller's requested key: suggest an alternative user key. */
			if(mBoxRes != IDYES) return E_ACCESSDENIED;
			hKey_ClassRoot.InitPath(HKEY_CURRENT_USER, _T("SOFTWARE\\Classes"));
			/* If this fails, we'll fail further down the line. */
		}

		try {
			dwErr = RegKey.create();
			// if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 1st key"));
			/* We expect this fail. The shell extension just won't get approved. */
			dwErr = RegKey.put_value(CLSID_IFilePermsBox_ToString, _T("FilePermsBox Property Sheet"));
			/* Add to Approved ShellExts */
			dwErr = 0;

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("CLSID\\") + CLSID_IFilePermsBox_ToString);
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 2nd key"));
			dwErr = RegKey.put_value(_T(""), _T("FilePermsBox Sheet"));
			/* CLSID\CLSID_FilePermsBox */

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("CLSID\\") + CLSID_IFilePermsBox_ToString + _T("\\InProcServer32"));
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 3rd key"));
			dwErr = RegKey.put_value(_T("ThreadingModel"), _T("Apartment"));
			/* Find the name of ourselves (FilePermsBox.dll innit! Isn't it?). */
			sized_array<TCHAR> DllFileName (FILENAME_MAX + 1);

			DWORD dwLen = 0;
			do {/* GetModuleFileName shall tell us if we're called "FilePermsBox.dll". */
				DllFileName.reset(DllFileName.get_Size() * 2);
				dwLen = ::GetModuleFileName(::g_hInst, DllFileName.get(), FILENAME_MAX + 1);
				/* IF we are nearing the limits of the buffer, reallocate. */
			} while (DllFileName.get_Size() - dwLen < 2);
			dwErr = RegKey.put_value(_T(""), DllFileName.get());

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("Directory\\shellex\\PropertySheetHandlers\\FilePermsBox") );
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 4th key"));
			dwErr = RegKey.put_value(_T(""), CLSID_IFilePermsBox_ToString);
			/* Directory property sheet extension */

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("*\\shellex\\PropertySheetHandlers\\FilePermsBox"));
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 5th key"));
			dwErr = RegKey.put_value(_T(""), CLSID_IFilePermsBox_ToString);
			/* All files property sheet extension */

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("Drive\\shellex\\PropertySheetHandlers\\FilePermsBox") );
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 6th key"));
			dwErr = RegKey.put_value(_T(""), CLSID_IFilePermsBox_ToString);
			/* Drives property sheet extension */

			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("CLSID\\") + CLSID_IFilePermsBox_ToString + _T("\\VersionIndependentProgID"));
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 7th key"));
			dwErr = RegKey.put_value(_T(""), _T("FilepermsBox.SecurityBox"));
			/* CLSID\IID_FilePermsBox\VersionIndependentProgID */


			RegKey.InitPath(hKey_ClassRoot.get_key(), _T("CLSID\\") + CLSID_IFilePermsBox_ToString + _T("\\ProgID"));
			dwErr = RegKey.create();
			if(dwErr != ERROR_SUCCESS) throw STD(_T("Could not create 8th key"));
			dwErr = RegKey.put_value(_T(""), _T("FilepermsBox.SecurityBox.1"));
			/* CLSID\IID_FilePermsBox\VersionIndependentProgID */

		} catch (const std::basic_string<TCHAR> &e)
		{/* Die at the first sign of error. */
			TError << RegKey.get_TError().str().c_str() << _T("\nError: ") << e.c_str();
			dwErr = static_cast<HRESULT>(SELFREG_E_CLASS);
			SuppressibleMsgBox(Options_Flags, NULL, TError.str().c_str(), _T(""), MB_RETRYCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON2);
		}

		return HRESULT_FROM_WIN32(dwErr);
	}
	else
	{/* Uninstall Mode. DllUnregisterServer() */
		if(!(Options_Flags & MEONLY))
		{
			hKey_ClassRoot.InitPath(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Classes"));
			EmptyHKCRRegistry(hKey_ClassRoot.get_key(), RegKey, CLSID_IFilePermsBox_ToString);
		}
		/* We must continue even if anything fails. */
		RegKey.InitPath(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"));
		RegKey.remove_value(CLSID_IFilePermsBox_ToString);

		if(!(Options_Flags & ALLUSERSONLY))
		{/* Now delete the user's HKCR. */
			hKey_ClassRoot.InitPath(HKEY_CURRENT_USER, _T("SOFTWARE\\Classes"));
			dwErr = EmptyHKCRRegistry(hKey_ClassRoot.get_key(), RegKey, CLSID_IFilePermsBox_ToString);
		}

		if(RegKey.get_TError().str().size() > 2)
		{/* The log should be empty here. */
			SuppressibleMsgBox(Options_Flags, NULL, RegKey.get_TError().str().c_str(),
				_T("FilePermsBox Uninstall Log"), MB_OK | MB_DEFBUTTON1);
		}
	}

	return HRESULT_FROM_WIN32(dwErr);
}



STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
{/* Allocate the CShellExtClassFactory and set ppv to this object. */

	HRESULT dwErr = E_FAIL;
	if(::IsBadReadPtr(ppv, sizeof(void*))) return E_INVALIDARG;
	if(CheckVersion() != TRUE) return HRESULT_FROM_WIN32(ERROR_CALL_NOT_IMPLEMENTED);

	*ppv = NULL;
	if(::IsEqualIID(rclsid, CLSID_IFilePermsBox))
	{/* Send the Shell the Interface to use. */
		CShellClassFactory *pcf = new CShellClassFactory();
		dwErr = pcf->QueryInterface(riid, ppv);
		if(dwErr != S_OK)
		{
			pcf->Release();
			return dwErr;
		}
		return S_OK;
	}

	return CLASS_E_CLASSNOTAVAILABLE;
}



DWORD EmptyHKCRRegistry(HKEY hKey_ClassRoot, registry_string &RegKey, const std::basic_string<TCHAR> &CLSID_IFilePermsBox_ToString)
{/* This is a minor procedure to delete the specified registry keys. A helper for DllUnRegisterServer */
	/* Continue even if it fails. */
	DWORD dwErr = 0;

	RegKey.InitPath(hKey_ClassRoot, _T("*\\shellex\\PropertySheetHandlers\\FilePermsBox"));
	dwErr |= RegKey.remove_key(TRUE);

	RegKey.InitPath(hKey_ClassRoot, _T("Directory\\shellex\\PropertySheetHandlers\\FilePermsBox"));
	dwErr |= RegKey.remove_key(TRUE);

	RegKey.InitPath(hKey_ClassRoot, _T("Drive\\shellex\\PropertySheetHandlers\\FilePermsBox"));
	dwErr |= RegKey.remove_key(TRUE);

	RegKey.InitPath(hKey_ClassRoot, _T("CLSID\\") + CLSID_IFilePermsBox_ToString);
	dwErr |= RegKey.remove_key(TRUE);

	return dwErr;
}



int FlagError(HWND TheirhWnd, const std::basic_string<TCHAR> &ErrorText, const std::basic_string<TCHAR> &ErrorTitle)
{/* Displays an error for the window TheirhWnd. */
	HWND WndParent = ::GetParent(TheirhWnd);
	if(WndParent == NULL) WndParent = TheirhWnd;
	return ::MessageBox(TheirhWnd, ErrorText.c_str(), ErrorTitle.c_str(), MB_OK);
}



int SuppressibleMsgBox(__int64 Options_Flags, HWND TheirhWnd, LPCTSTR CaptionText, LPCTSTR WindowTitle, UINT mbType)
{/** This function is a wrapper for MessageBox. However, if Options_Flags contains the SILENT flag, then the
*	messagebox is not shown and the DEFBUTTON is selected (if DEFBUTTON is not included).
*	If incompatible parameters are specified (eg. mbType == MB_DEFBUTTON2 | MB_OK), then a string exception is thrown.
*	(don't handle these errors, they should be treated as asserts).
**/
	if(Options_Flags & SILENT)
	{/* The silent flag was selected, which button was defaulted. We evaluate the DEFBUTTONs in order */
		if(mbType == MB_DEFBUTTON1)
		{/* return the correct ID according to what the mbType specified. */
			if(mbType & MB_ABORTRETRYIGNORE)
				return IDABORT;
			if(mbType & MB_CANCELTRYCONTINUE)
				return IDCANCEL;
			if(mbType == MB_OK)
				return IDOK;
			if(mbType & MB_OKCANCEL)
				return IDOK;
			if(mbType & MB_RETRYCANCEL)
				return IDRETRY;
			if(mbType & MB_YESNO)
				return IDYES;
			if(mbType & MB_YESNOCANCEL)
				return IDYES;
			throw STD(_T("incompatible parameters were passed to SuppressibleMsgBox()"));
		}
		if(mbType & MB_DEFBUTTON2)
		{/* DEFBUTTON2 has different parameters when used. */
			if(mbType & MB_ABORTRETRYIGNORE)
				return IDRETRY;
			if(mbType & MB_CANCELTRYCONTINUE)
				return IDTRYAGAIN;
			if(mbType & MB_OKCANCEL)
				return IDCANCEL;
			if(mbType & MB_RETRYCANCEL)
				return IDCANCEL;
			if(mbType & MB_YESNO)
				return IDNO;
			if(mbType & MB_YESNOCANCEL)
				return IDNO;
			throw STD(_T("incompatible parameters were passed to SuppressibleMsgBox()"));
		}
		if(mbType & MB_DEFBUTTON3)
		{/* You get the idea now */
			if(mbType & MB_ABORTRETRYIGNORE)
				return IDIGNORE;
			if(mbType & MB_CANCELTRYCONTINUE)
				return IDCONTINUE;
			if(mbType & MB_YESNOCANCEL)
				return IDCANCEL;
			throw STD(_T("incompatible parameters were passed to SuppressibleMessageBox()"));
		}
		/* MB_DEFBUTTON4 is illegal in all cases. And lack of DEFBUTTON is also illegal. */
		throw STD(_T("incompatible parameters were passed to SuppressibleMessageBox"));
	}
	else return ::MessageBox(TheirhWnd, CaptionText, WindowTitle, mbType);
	/* For Interactive installs, just filter right through to MessageBox. */
}


BOOL EnablePropertySheetControls(HWND TheirhWnd, BOOL bEnable)
{/* Enables/disables the controls on the specified instance of the PageDlgProc */
	BOOL Result = TRUE;
	Result = Result & ::EnableWindow(::GetDlgItem(TheirhWnd, IDC_CHECK1), bEnable);
	Result = Result & ::EnableWindow(::GetDlgItem(TheirhWnd, IDC_BUTTON2), bEnable);
	Result = Result & ::EnableWindow(::GetDlgItem(TheirhWnd, IDC_ABOUT), bEnable);
	return Result;
}



INT_PTR CALLBACK /* CShellExt:: */PageDlgProc(HWND TheirhWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{/** This is the basic property page we see when the shell opens our object.
*
**/
	switch(Msg)
	{
		case WM_INITDIALOG:
		{/* lParam contains important info. It contains the Propsheetpage structure that we used to create this box. */
			PROPSHEETPAGE *psp = reinterpret_cast<PROPSHEETPAGE *>(lParam);
			if( ::IsBadReadPtr(psp, sizeof(PROPSHEETPAGE)) )
			{/* Now we should extract the lParam of psp */
				::SetWindowLongPtr(TheirhWnd, DWLP_MSGRESULT, FALSE);
				return FALSE;
			}

			CShellExt *ThisClass = reinterpret_cast<CShellExt *>(psp->lParam);
			if( ::IsBadReadPtr(ThisClass, sizeof(CShellExt)) )
			{/* Get ThisClass from the PROPSHEETPAGE struct */
				::SetWindowLongPtr(TheirhWnd, DWLP_MSGRESULT, FALSE);
				return FALSE;
			}

			if( ::SetProp(TheirhWnd, _T("ComObjClassPtr"), reinterpret_cast<HANDLE>(ThisClass)) != TRUE)
			{/* Dump this into the window property */
				::SetWindowLongPtr(TheirhWnd, DWLP_MSGRESULT, FALSE);
				return FALSE;
			}


			/* Set the text */
			::SetDlgItemText(TheirhWnd, IDC_EDIT0, _T("The text below represents the security descriptor for this ")
				_T("object. To view/edit the security descriptor, press the button below."));
			::SetDlgItemText(TheirhWnd, IDC_EDIT1, _T("Determining security. Please wait."));
			::CheckDlgButton(TheirhWnd, IDC_CHECK1, TRUE);
			::SetDlgItemText(TheirhWnd, IDC_BUTTON2, _T("Read..."));

			{/* Resize the Window. */
				RECT RectDlg = {0};
				::GetWindowRect(TheirhWnd, &RectDlg);
				RectDlg.bottom++, RectDlg.right++;
				::MoveWindow(TheirhWnd, RectDlg.left, RectDlg.top,
					RectDlg.right - RectDlg.left, RectDlg.bottom - RectDlg.top, TRUE);
			}

			return TRUE;
		}

		case WM_NOTIFY:
		{
			::SetWindowLongPtr(TheirhWnd, DWLP_MSGRESULT, FALSE);
			switch (reinterpret_cast<NMHDR *>(lParam)->code)
			{
				case PSN_APPLY:
				case PSN_SETACTIVE:
				{/* Retrieve the FileNamesCollection. TODO: Finish testing multi-file support. */
					CShellExt *ThisClass = reinterpret_cast<CShellExt *>(::GetProp(TheirhWnd, _T("ComObjClassPtr")));
					if(::IsBadReadPtr(ThisClass, sizeof(CShellExt)))
						return FALSE;

					HANDLE hCursor = ::LoadImage(NULL, MAKEINTRESOURCE(OCR_WAIT), IMAGE_CURSOR,
						0, 0, LR_DEFAULTSIZE | LR_SHARED);
					if(hCursor != NULL)
						SetClassLongPtr(TheirhWnd, GCLP_HCURSOR, LONG_PTR2(hCursor));

					std::basic_string<TCHAR> ppSD = _T("An error occurred trying to obtain the security descriptor");
					std::basic_string<TCHAR> ppSD1st;
					GetSDForObject(ThisClass->FileNamesCollection.front(), ppSD1st, FALSE);

					for(std::list<const std::basic_string<TCHAR> >::const_iterator Iter =
						ThisClass->FileNamesCollection.begin(); Iter != ThisClass->FileNamesCollection.end();
						Iter++)
					{/** The string security descriptors for each object must be equal to the first
					**/
						const std::basic_string<TCHAR> FileName = *Iter;
						GetSDForObject(FileName, ppSD, FALSE);
						SECURITY_INFORMATION psi = CompareSDs(ppSD, ppSD1st);
						if(psi != 0)
						{/* Show a message to the user if one of the psis are inconsistent. */
							if( ::MessageBox(TheirhWnd,
								_T("The objects you've selected do not have the same security descriptor. ")
								_T("Do you want FilePermsBox to correct this and reset the security descriptors?"),
								_T("FilePermsBox - Fix Security Descriptors"), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON1) != IDYES)
							{
								::SetDlgItemText(TheirhWnd, IDC_EDIT1,
									_T("Unable to display multiple security descriptors."));

								/* Put back the old cursor */
								hCursor = ::LoadImage(NULL, MAKEINTRESOURCE(OCR_NORMAL), IMAGE_CURSOR,
									0, 0, LR_DEFAULTSIZE | LR_SHARED);
								if(hCursor != NULL)
									SetClassLongPtr(TheirhWnd, GCLP_HCURSOR, LONG_PTR2(hCursor));
								return FALSE;
							}/* Else correct the security descriptors */
							FixSDs(TheirhWnd, psi, ThisClass->FileNamesCollection);
							Iter = ThisClass->FileNamesCollection.begin(); /* And recheck SDs. */
							GetSDForObject(ThisClass->FileNamesCollection.front(), ppSD1st, FALSE);
						}
					}
					::SetDlgItemText(TheirhWnd, IDC_EDIT1, ppSD.c_str());

					/* Now the controls are safe to enable */
					hCursor = ::LoadImage(NULL, MAKEINTRESOURCE(OCR_NORMAL), IMAGE_CURSOR,
						0, 0, LR_DEFAULTSIZE | LR_SHARED);
					if(hCursor != NULL)
						::SetClassLongPtr(TheirhWnd, GCLP_HCURSOR, LONG_PTR2(hCursor));

					EnablePropertySheetControls(TheirhWnd, TRUE);
					break;
				}

				default:
					break;
			}
			::SetWindowLongPtr(TheirhWnd, DWLP_MSGRESULT, TRUE);
			return FALSE;
		}

		case WM_SIZE:
		{/* WM_SIZE. */
			const int space = 4;
			RECT RectDlg = {0}, RectCtl = {0};

			{/* Get The Client area of the Window. */
				::GetClientRect(TheirhWnd, &RectDlg);
				RectDlg.left += 7;
				RectDlg.top += 7;
				RectDlg.bottom -= 7;
				RectDlg.right -= 7;

				/* Resize IDC_TITLE to right of RectDlg. */
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_EDIT0), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectCtl.bottom = RectCtl.bottom - RectCtl.top; /* This must be calculated first */
				RectCtl.left = RectDlg.left;
				RectCtl.right = RectDlg.right - RectDlg.left;
				RectCtl.top = RectDlg.top;
				::SetWindowPos(::GetDlgItem(TheirhWnd, IDC_EDIT0), NULL, RectCtl.left, RectCtl.top,
					RectCtl.right - RectCtl.left, RectCtl.bottom, SWP_NOCOPYBITS | SWP_NOZORDER);

				/* Move IDC_CHECK1 to bottom left of window. */
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_CHECK1), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectCtl.left = 2 * space;
				RectCtl.top = RectDlg.bottom + RectCtl.top - RectCtl.bottom;
				::SetWindowPos(::GetDlgItem(TheirhWnd, IDC_CHECK1),
					NULL, RectCtl.left, RectCtl.top, 0, 0, SWP_NOSIZE | SWP_NOCOPYBITS | SWP_NOZORDER);

				/* Move IDC_BUTTON2 to bottom right of window. */
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_BUTTON2), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectCtl.left = RectDlg.right + RectCtl.left - RectCtl.right;
				RectCtl.top = RectDlg.bottom + RectCtl.top - RectCtl.bottom;
				::SetWindowPos(::GetDlgItem(TheirhWnd, IDC_BUTTON2),
					NULL, RectCtl.left, RectCtl.top, 0, 0, SWP_NOSIZE | SWP_NOCOPYBITS | SWP_NOZORDER);
				const int x = RectCtl.left - space;

				/* Move IDC_ABOUT beside IDC_BUTTON2. */
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_ABOUT), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectCtl.left = x + RectCtl.left - RectCtl.right;
				RectCtl.top = RectDlg.bottom + RectCtl.top - RectCtl.bottom;
				::SetWindowPos(::GetDlgItem(TheirhWnd, IDC_ABOUT),
					NULL, RectCtl.left, RectCtl.top, 0, 0, SWP_NOSIZE | SWP_NOCOPYBITS | SWP_NOZORDER);

				/* IDC_EDIT1. The top needs to be below the title, and the bottom needs to be above the button. */
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_EDIT0), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectDlg.top = RectCtl.bottom + space;
				RectDlg.left = space;
				RectDlg.right = RectCtl.right;
				::GetWindowRect(::GetDlgItem(TheirhWnd, IDC_BUTTON2), &RectCtl);
				::MapWindowPoints(NULL, TheirhWnd, reinterpret_cast<LPPOINT>(&RectCtl), sizeof(RECT) / sizeof(POINT));
				RectDlg.bottom = RectCtl.top - space;
				::CopyRect(&RectCtl, &RectDlg);
				::SetWindowPos(::GetDlgItem(TheirhWnd, IDC_EDIT1), NULL, RectCtl.left, RectCtl.top,
					RectCtl.right - RectCtl.left, RectCtl.bottom - RectCtl.top, SWP_NOCOPYBITS | SWP_NOZORDER);
			}

			::InvalidateRgn(TheirhWnd, NULL, FALSE);
			::UpdateWindow(TheirhWnd);
			return TRUE;
		}
		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDC_ABOUT:
				{/* The about button was pressed. */
					::ShowAbout(TheirhWnd);
					break;
				}
				case IDC_BUTTON2:
				{/* The "Read..." button was pressed: go to the new dialog box. */
					CShellExt *ThisClass = reinterpret_cast<CShellExt *>(::GetProp(TheirhWnd, _T("ComObjClassPtr")));
					if(::IsBadReadPtr(ThisClass, sizeof(CShellExt)))
						return FALSE;
					UINT DoReadOnly = ::IsDlgButtonChecked(TheirhWnd, IDC_CHECK1);
					/* Disable all Controls */
					EnablePropertySheetControls(TheirhWnd, FALSE);

					/* Call the API! */
					::DoSecurityBox(TheirhWnd, SE_FILE_OBJECT, ThisClass->FileNamesCollection, !DoReadOnly);

					{/* Reenable all controls and update the security descriptor */
						EnablePropertySheetControls(TheirhWnd, TRUE);
						std::basic_string<TCHAR> SDDLString = _T("An error occurred retrieving the security descriptor");
						::GetSDForObject(ThisClass->FileNamesCollection.front(), SDDLString, TRUE);
						::SetDlgItemText(TheirhWnd, IDC_EDIT1, SDDLString.c_str());
					}

					break;
				}
				case IDC_CHECK1:
				{/* Make sure the button has the right text (and get the CURRENT state of Check1, not some saved state) */
					UINT DoReadOnly = ::IsDlgButtonChecked(TheirhWnd, IDC_CHECK1);
					if(DoReadOnly == TRUE) ::SetDlgItemText(TheirhWnd, IDC_BUTTON2, _T("Read..."));
					else ::SetDlgItemText(TheirhWnd, IDC_BUTTON2, _T("Edit..."));
				}
				case IDOK:
					break;
				default:
					break;
			}
			return FALSE;
		}

		case WM_DESTROY:
		{/* Remove the stored Window Property. */
			::RemoveProp(TheirhWnd, _T("ComObjClassPtr"));
			return TRUE;
		}

		case WM_CLOSE:
		{/* WM_CLOSE? you mean WM_DESTROY! */
			::DestroyWindow(TheirhWnd);
			return FALSE;
		}

		default:
			break;
	}

	return FALSE;
}



UINT CALLBACK /* CShellExt:: */PageCallbackProc(HWND, UINT uMsg, PROPSHEETPAGE *ppsp)
{/* The two things we need to do: return TRUE in creation, and call release() on this when the window is destroyed. */
	switch(uMsg)
	{
		case PSPCB_CREATE:
		{
			return TRUE;
		}
		case PSPCB_RELEASE:
		{/* This is our chance to call Release */
			CShellExt *ThisClass = reinterpret_cast<CShellExt *>(ppsp->lParam);
			if(::IsBadReadPtr(ThisClass, sizeof(CShellExt))) return FALSE;
			ThisClass->Release();
		}
		default:
			break;
	}
	return FALSE;
}


STDMETHODIMP
CShellClassFactory::QueryInterface(REFIID iid, void **ppv)
{/* OLE implementation interface */
	if(::IsBadReadPtr(ppv, sizeof(void*))) return E_INVALIDARG;
	*ppv = NULL;
	if(::IsEqualIID(iid, IID_IUnknown) || ::IsEqualIID(iid, IID_IClassFactory))
	{
		*ppv = dynamic_cast<IClassFactory *>(this);
		AddRef();
		return S_OK;
	}
	return E_NOINTERFACE;
}



STDMETHODIMP_(ULONG)
CShellClassFactory::AddRef(void)
{/* OLE */
	return ::InterlockedIncrement(&m_cRef);
}



STDMETHODIMP_(ULONG)
CShellClassFactory::Release(void)
{/* OLE */
	if (::InterlockedDecrement(&m_cRef) == 0)
	{
		delete this;
		return 0;
	}

	return m_cRef;
}



STDMETHODIMP
CShellClassFactory::LockServer(BOOL /* DoLock */)
{/* Don't need to implement this */
	return S_OK;
}



STDMETHODIMP
CShellClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv)
{/* Set pUnkOuter to the CShellExt interface. */
	HRESULT dwErr = E_NOINTERFACE;
	if(ppv == NULL)
		return E_INVALIDARG;
	*ppv = NULL;
	if(pUnkOuter != NULL)
		return CLASS_E_NOAGGREGATION;

	if(::IsEqualIID(riid, IID_IShellExtInit))
	{
		CShellExt *ShellExt = new CShellExt();
		dwErr = ShellExt->QueryInterface(riid, ppv);
	}

	return dwErr;
}








CShellExt::CShellExt() : m_cRef(0), hWnd(NULL)
{/* Increment the global reference count. */
	::InterlockedIncrement(const_cast<volatile LONG *>(reinterpret_cast<LONG *>(&g_RefCount)));
}


CShellExt::~CShellExt()
{/* decrement the global reference count. */
	if(g_RefCount > 0)
		::InterlockedDecrement(const_cast<volatile LONG *>(reinterpret_cast<LONG *>(&g_RefCount)));
}


STDMETHODIMP_(ULONG)
CShellExt::AddRef(void)
{/* OLE */
	return ::InterlockedIncrement(&m_cRef);
}


STDMETHODIMP_(ULONG)
CShellExt::Release(void)
{/* OLE */
	if (::InterlockedDecrement(&m_cRef) == 0)
	{
		delete this;
		return 0;
	}

	return m_cRef;
}


STDMETHODIMP
CShellExt::QueryInterface(REFIID riid, LPVOID *ppv)
{/* We implement IShellExtInit, IUnknown, and IShellPropSheetExt */
	*ppv = NULL;
	if(::IsEqualIID(riid, IID_IUnknown) || ::IsEqualIID(riid, IID_IShellExtInit))
	{
		*ppv = dynamic_cast<IShellExtInit *>(this);
		AddRef();
		return S_OK;
	}
	if(::IsEqualIID(riid, IID_IShellPropSheetExt))
	{
		*ppv = dynamic_cast<IShellPropSheetExt *>(this);
		AddRef();
		return S_OK;
	}
	return E_NOINTERFACE;
}



STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST /*pidlFolder*/, IDataObject *DataObj, HKEY /*ProgId*/)
{/* Duplicate the object pointer and registry handle */
	if ( ::IsBadReadPtr(DataObj, sizeof(IDataObject)) ) return E_INVALIDARG;

	DataObj->AddRef();
	/* Get an IDataObject Interface pointer. */

	STGMEDIUM medium = {0};
	FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};

	if( SUCCEEDED(DataObj->GetData(&fe, &medium)) )
	{/* Get the count of files dropped. */
		try {
			UINT uCount = ::DragQueryFile(reinterpret_cast<HDROP>(medium.hGlobal), 0xFFFFFFFF, NULL, 0);

			for(UINT i = 0; i < uCount; i++)
			{/* Get the first file name from the CF_HDROP.  */
				int len = ::DragQueryFile(reinterpret_cast<HDROP>(medium.hGlobal), i, NULL, 0);
				if(len < 1) continue;

				/* Allocate data and do the filename query */
				sized_array<TCHAR> sFName (len + FILENAME_MAX + 1);
				::DragQueryFile(reinterpret_cast<HDROP>(medium.hGlobal), i, sFName.get(), len + FILENAME_MAX + 1);

				{
					std::basic_string<TCHAR> ThisFileName = sFName.get();
					/* Convert relative paths to full paths. */
					if(::_tfullpath(sFName.get(), ThisFileName.c_str(), FILENAME_MAX + len + 1) != NULL)
						ThisFileName = sFName.get();

					/* And add it to this->Collection */
					FileNamesCollection.push_back(ThisFileName.c_str());
				}
			}

		} catch (...) {/* finally */
			::ReleaseStgMedium(&medium);
			DataObj->Release();
			throw;
		}
		::ReleaseStgMedium(&medium);
	}

	/* Finally cleanup resources. */
	DataObj->Release();
	return S_OK;
}



STDMETHODIMP
CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
{/* We must return the function pointer to the callback (you can use LPARAM to pass a structure to WM_INITDIALOG). */
	PROPSHEETPAGE psp = {0};

	if(this->ShouldWeCreatePage() != TRUE)/* Determine if we should show this page */
		return (HRESULT_FROM_WIN32(ERROR_NO_SECURITY_ON_OBJECT));

	/* Fill up the PROPSHEETPAGE struct */
	psp.dwSize		= sizeof(psp);
	psp.dwFlags		= PSP_DEFAULT | PSP_USETITLE | PSP_USEREFPARENT | PSP_USECALLBACK;
	psp.hInstance	= g_hInst;
	psp.pszTemplate	= MAKEINTRESOURCE(IDD_PAGEDLG);
	psp.hIcon		= 0;
	psp.pszTitle	= _T("FilePermsBox");
	psp.pfnDlgProc	= /* this-> */ PageDlgProc;
	psp.pcRefParent	= &g_RefCount;
	psp.pfnCallback	= /* this-> */ PageCallbackProc;	/* This is where Release() is called */
	psp.lParam		= reinterpret_cast<LPARAM>(this);

	/* Create the page */
	HPROPSHEETPAGE hPage = ::CreatePropertySheetPage(&psp);
	if(hPage == NULL) return E_OUTOFMEMORY;

	if(lpfnAddPage(hPage, lParam) != TRUE)
	{/* Send the dialog box to the shell. */
		::DestroyPropertySheetPage(hPage);
		return E_FAIL;
	}

	/* Increment our own self reference. */
	this->AddRef();
	return S_OK;
}


BOOL CShellExt::ShouldWeCreatePage(void)
{/**	There are a number of reasons why we shouldn't create a property sheet.
*	The objects Must be of the same type (no mixing drives and files).
*	And All objects must reside on drives that support ACLs.
*	If we have trouble accessing any file (non-ACL based), we Mustn't continue.
**/

	if(FileNamesCollection.size() < 1)/* FileNames must be filled up */
		return FALSE;

	if(CheckVersion() != TRUE)/* Windows Must be 2000 or better. */
		return FALSE;

	/* Find out what this file is. */
	DWORD DirectoryAttrib = ::GetFileAttributes(FileNamesCollection.front().c_str());
	/* clear out the non-pertinent flags. */
	if(DirectoryAttrib == INVALID_FILE_ATTRIBUTES)
		return FALSE;


	size_t LastSlash = FileNamesCollection.front().find_last_of(L"/\\:");
	/* The files must reside in the same directory. */

	for(std::list<const std::basic_string<TCHAR> >::const_iterator Iter = this->FileNamesCollection.begin();
		Iter != FileNamesCollection.end(); Iter++)
	{/* Each Filename Must reside on NTFS drives. */
		std::basic_string<TCHAR> FName = *Iter;
		DWORD FileSystemFlags = 0;
		FName = FName.substr(0, 3);

		::GetVolumeInformation(FName.c_str(), NULL, 0, NULL, NULL, &FileSystemFlags, NULL, 0);
		if(! (FileSystemFlags & FS_PERSISTENT_ACLS) )
		{/* Check if the volume supports ACLs */
			return FALSE;
		}

		/* Each FileName must be of the same type. */
		DWORD FileAttrib = ::GetFileAttributes(Iter->c_str());
		if(FileAttrib == INVALID_FILE_ATTRIBUTES) return FALSE;

		if(FileAttrib & FILE_ATTRIBUTE_REPARSE_POINT)/* Completely avoid junctions */
			return FALSE;

		if( (FileAttrib & FILE_ATTRIBUTE_DIRECTORY) && !(DirectoryAttrib & FILE_ATTRIBUTE_DIRECTORY))
		{/* If this is a directory, then DirectoryAttrib needs to be a directory. */
			return FALSE;
		}
		if((DirectoryAttrib & FILE_ATTRIBUTE_DIRECTORY) && !(FileAttrib & FILE_ATTRIBUTE_DIRECTORY))
		{/* If Directoryattrib is a directory, then this needs to be a directory. */
			return FALSE;
		}

		if(Iter->find_last_of(L"/\\:") != LastSlash)
			return FALSE;
		/** The last slash needs to be at the same position as the first file (not a perfect test for same directory)
		*	but at least it's better than what the shell does.
		**/
	}

	return TRUE;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

oshah
Web Developer
United States United States
Mr. Shah is a reclusive C++/C# developer lurking somewhere in the depths of the city of London. He learnt physics at Kings' College London and obtained a Master in Science there. Having earned an MCAD, he teeters on the brink of transitioning from C++ to C#, unsure of which language to jump to. Fortunately, he also knows how to use .NET interop to merge code between the two languages (which means he won't have to make the choice anytime soon).
 
His interests (apart from programming) are walking, football (the real one!), philosophy, history, retro-gaming, strategy gaming, and any good game in general.
 
He maintains a website / blog / FAQ / junk at shexec32.serveftp.net, where he places the best answers he's written to the questions you've asked. If you can find him, maybe you can hire Mr. Shah to help you with anything C++[/CLI]/C#/.NET related Smile | :) .

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 7 Sep 2005
Article Copyright 2005 by oshah
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid