How to sign on existed pdf using podofo? - podofo

I have a project which uses PoDoFo for digitally signing on pdf. i have done successfully digital signature on new pdf file and how to do on existed pdf file?
Here is my code :
PoDoFo::PdfPage* pPage;
const char *path1=[path UTF8String];
PoDoFo::PdfSignOutputDevice signer(path1);
// Reserve space for signature
signer.SetSignatureSize(2048);
PoDoFo::PdfStreamedDocument writer( &signer, PoDoFo::ePdfVersion_1_5 );
// Disable default appearance
writer.GetAcroForm(PoDoFo::ePdfCreateObject, PoDoFo::PdfAcroForm::ePdfAcroFormDefaultAppearance_None);
pPage = writer.CreatePage(PoDoFo::PdfPage::CreateStandardPageSize(PoDoFo::ePdfPageSize_A4 ) );
TEST_SAFE_OP( CreateSimpleForm( pPage, &writer, *signer.GetSignatureBeacon() ) );
TEST_SAFE_OP( writer.Close() );

Related

Decode PKCS#7 Signature via Windows API?

I wish to parse and display the contents of an Authenticode PKCS#7 signature as extracted from a Window PE binary's Security Directory.
I can use OpenSSL to do this on the command line with "openssl pkcs7 -text -in extracted_signature.pks -inform DER -print_certs", however I need to do this via C/C++ and the Windows API. I cannot use the OpenSSL library itself.
Using the CryptDecodeObjectEx API I can begin to decode the extracted signature:
CRYPT_CONTENT_INFO * content_info;
DWORD len;
CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
PKCS_CONTENT_INFO,
pointer_to_extracted_signature,
length_of_extracted_signature,
CRYPT_DECODE_ALLOC_FLAG,
NULL,
&content_info,
&len
);
The above call completes successfully and content_info->pszObjId will have an OID of "1.2.840.113549.1.7.2" (szOID_RSA_signedData) however I am unable to find the structures needed to continue decoding. The available OID's for CryptDecodeObjectEx are listed here.
Can anybody please advise how to decode an Authenticode PKCS#7 signature via the Windows API?
I have found the correct way to decode an Authenticode PKCS#7 signature is to use CryptQueryObject with the CERT_QUERY_OBJECT_BLOB and CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED flags set. Code snippit below for anybody who might need to do this.
CERT_BLOB cert_blob;
HCERTSTORE cert_store = NULL;
HCRYPTMSG cert_msg = NULL;
cert_blob.pbData = pointer_to_extracted_signature;
cert_blob.cbData = length_of_extracted_signature;
CryptQueryObject(
CERT_QUERY_OBJECT_BLOB,
&cert_blob,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
NULL,
NULL,
&cert_store,
&cert_msg,
NULL
);
PCCERT_CONTEXT next_cert = NULL;
while( (next_cert = CertEnumCertificatesInStore( cert_store, next_cert ) ) != NULL )
{
// process next_cert...
}

Find out number of icons in an icon resource using Win32 API

I have an *.ico file that contains multiple icons in different sizes linked to my executable as a resource. I use this resource to set my application's icon with RegisterClassEx(), i.e.:
wcx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
In addition to that, I'd also like to convert all the single icons in this resource to ARGB pixel arrays. This should be possible by using GetDIBits() on the bitmap returned by GetIconInfo().
However, there is one problem: I need to find out the number of icons in the HICON handle returned by LoadIcon() as well as their sizes. I do not seem to find an API that takes a HICON handle and tells me how many icons there are actually in there and what their sizes are.
Is this possible somehow or do I need to go the hard way and parse the *.ico resource myself?
The original icon handling functions are ancient. They were introduced in 16-bit Windows and designed for a system that defined only one icon size. Therefore, most of those functions are unaware of the possibility of more than one icon size. To get to the differently sized icons in a resource additional work is required.
Following the ICO file format, consisting of an icon directory and the actual icon images, an icon resource consists of two parts as well: The icon directory of type RT_GROUP_ICON and the individual icons (RT_ICON). The directory is represented by the following structures:
#pragma pack( push )
#pragma pack( 2 )
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource type (1 for icons)
WORD idCount; // How many images?
GRPICONDIRENTRY idEntries[1]; // The entries for each image
} GRPICONDIR, *LPGRPICONDIR;
#pragma pack( pop )
and
#pragma pack( push )
#pragma pack( 2 )
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // how many bytes in this resource?
WORD nID; // the ID
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
#pragma pack( pop )
The icon directory for an icon group ID can be retrieved using the following code:
typedef std::list<GRPICONDIRENTRY> IconDirectory;
IconDirectory GetIconDirectory( HMODULE hMod, WORD Id ) {
HRSRC hRsrc = FindResourceW( hMod, MAKEINTRESOURCE( Id ), RT_GROUP_ICON );
HGLOBAL hGlobal = LoadResource( hMod, hRsrc );
GRPICONDIR* lpGrpIconDir = (GRPICONDIR*)LockResource( hGlobal );
IconDirectory dir;
for ( size_t i = 0; i < lpGrpIconDir->idCount; ++i ) {
dir.push_back( lpGrpIconDir->idEntries[ i ] );
}
return dir;
}
With the information from the icon directory the individual icons can be constructed with this code:
HICON LoadSpecificIcon( HMODULE hMod, WORD Id ) {
HRSRC hRsrc = FindResourceW( hMod, MAKEINTRESOURCE( Id ), RT_ICON );
HGLOBAL hGlobal = LoadResource( hMod, hRsrc );
BYTE* lpData = (BYTE*)LockResource( hGlobal );
DWORD dwSize = SizeofResource( hMod, hRsrc );
HICON hIcon = CreateIconFromResourceEx( lpData, dwSize, TRUE, 0x00030000,
0, 0, LR_DEFAULTCOLOR );
return hIcon;
}
Putting all the pieces together, the following loads explorer.exe as a resource file, retrieves the first icon group with ID 101 and prints the information from the icon directory for each entry. It then creates the individual icons and outputs the xHotspot and yHotspot data. For icons, the hotspot is located in the center:
void PrintIconDirEntry( const GRPICONDIRENTRY& DirEntry ) {
_wprintf_p( L"ID: %04d; width=%02d; height=%02d; bpp=%02d\n",
DirEntry.nID,
DirEntry.bWidth, DirEntry.bHeight, DirEntry.wBitCount );
}
void PrintIconInfo( HICON hIcon ) {
ICONINFO ii = { 0 };
GetIconInfo( hIcon, &ii );
_wprintf_p( L"xHotspot=%02d; yHotspot=%02d\n", ii.xHotspot, ii.yHotspot );
}
typedef std::list<GRPICONDIRENTRY>::const_iterator IconDirectoryCIt;
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hMod = LoadLibraryExW( L"C:\\Windows\\system32\\explorer.exe",
NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE );
IconDirectory dir = GetIconDirectory( hMod, 101 );
for ( IconDirectoryCIt it = dir.begin(); it != dir.end(); ++it ) {
PrintIconDirEntry( *it );
HICON hIcon = LoadSpecificIcon( hMod, it->nID );
PrintIconInfo( hIcon );
DestroyIcon( hIcon );
}
return 0;
}
To sum this up: Retrieving all icon sizes and color variations for an icon resources involves two steps:
Retrieve the icon directory with information for all icons in an icon group.
Iterate over the icon directory and construct the individual icons using CreateIconFromResourceEx.
References:
MSDN documentation for the icon format: Icons
The Old New Thing: The evolution of the ICO file format, part 1: Monochrome beginnings
The Old New Thing: The evolution of the ICO file format, part 2: Now in color!
The Old New Thing: The evolution of the ICO file format, part 3: Alpha-blended images
The Old New Thing: The evolution of the ICO file format, part 4: PNG images
The Old New Thing: The format of icon resources
The Old New Thing: How do I override the default icon selection algorithm?

How to decode the image which is a base64 string, in Dart?

I tried to get the image from clipboard when I press ctrl+v on a page, with the code from a gist, which will console.log the image content it gets.
I found the image content is a long base64 string with a special prefix:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAUCAYAAADStFABAAAJHElEQVRYCXVXWXObVxl+Pu2yFsuyLMnxHtdpY8c4pHXptOkEGKBlYDowYXLLLeGSX8D/gBvuErhgmElpJkCZNsswjhPZsbHGJo6Rl1pybMuyZEnWyvsc9xVfMvCOj8457/K8y9k+W5lMpi0EJcuywDn7ZrMJh8OBVqulYsMnT2WqSx3ylWivdnZM6lOPPRuJcpIdQ23IVz2Oyadvl8tlevLUL2XEUFv1Qx2S8lXnjAu4yPhfAdGARJmCaTAMgqS2drkGQrmOKVfHyrP3xFW5vbdj6Jg98eyLoPGTT1KZxklfHDudzk4hyNN8XDqgsT1YBkOi3G5gd8ixBkQHKqMd7SnTADRAlVGXpBjUI6mdYpGvTZNTPcUwhvJjT5oyO5baKJbmx7nZCQrSaDSMQzXQxGhAZXtgylOber1ukqat6qqO4tGeMi0Ie+oQw+6L+vYEFUftdFXZU6Z8xVBb+qKOxkNcEueKybnZCXamHVQB1IDO1AF56pR8DYRyNmIyOfI5VqKdYmhPHfJJ1FV7TZZ88tT2dd9qoxiqz95OikGe3eaVImgyqsy+tL+P9Bdf4sVCCrkXL8BNnDx/HqMzM5i8dg3heLyzDQlOGzpgQNprsTgvHOSwtnAfm2spvNxcNzq9g+cx8uY3ceHyhwj19BkesRiPLgRt7UXhziPv5cEe5pYeIb2xhMxORqxaGB4Yw1sjk3hv5kP09sRewWEs9mLRj5VOp83hpEPDEGClreVlzN2+jZ2nTzDY24PRRL8kCWRyWWwdHGLgyhXM3riB4elpkzwxtAjsX6ft50t4+vkfcPjPOYz0RzAy1G9UMlu7yOweITr1Li5/+7oU5LJJWBfl9aCJzfavjTTuPvgTlraWEB/pw7lBWZB2C7tbOSnwIS4NT+OHH3yCixPTncRZONrqwhDbRSYHFLBXYSGXw+Nbt1FIPcWvPv4IA9/7DlxX35elBhoPHmH73uf4zb2/4ImkEeztRXci8X8LQNxi/iVSf/89sDaHX/7ix4i9/xE8kQ/E2kI9/xAv/3EXt357B4tWG+FoAt29CRMPbUlaYDORn33B+/TBH7F2sIqf37yOH1z8PuLBKSPOlp7hs5V7uPW7O2g/bKEnEkNc8EjMk8SjSmL+zps3b/6aDrjVSKq0/NldbH/6Z9yYegtjM9Owjo7QfJJCK7UIq1BAMBJG8qSM1MIzuKQI56YmO9uYWBq04qXn7qH4+A5+8vE04rPvwapX0Th4hmZ+WSIqwxeNYsBVxOrjBTRCMfQNXTDfAmqvx4JFYeBfzv8ND59/gas/uoJvTb6NIkrYKK1gq7yKCsroC0dRD1aQWlxC2N2NccGz50dc3RUuChRYBeQdLC5gOuBHst5Ac2kF7e4wnD4fRWhWq2gVjpGUal7q8mN/cRH42fUzmRSAOHop6krmt5dwaTCMcMCH5r/XZPuEYXn8xqYlBWmWjo2MOuui63D8FLVarbMzGTAx9SNpfXcVfRdiCIR9SOdWEfSF4XG7DV5N8IqVE3SF/Yi/GQN1LesTI2NsjImLxJ7zzncCnZBUqZnZwoTfh2CpAp/jAI5qBfCdBe2WceukgnalggmvF9nNzVcuMGKQdPWIXTvZwbmhKLw+eZdRhaMpL4oUmNRqVGG1K0ZGnfTuziuBUoeYGhv7fHkX/VN9gN+BmlVDuXWMet1DVdSbNeGdwhJfyeE49ud3OzvdKMiP7gIWwhSBwbq/rqJW2+9xIeT2wh8IwRmU5Lu6IFHSGnA65M8Bv4zDMvXXz95rOuCdQjyuGLE4J3l9Hnh6uuDqCcMdDsESLMvlNTJHQz60vHJrO9rwFKQY+bLh057B6gKxJzZX0ev3IhIMIuj3yy7ww+f0wCuNBaq1nHC6BdPZRiR0iqL/2ODpD3G4y9hTv/NEEpxEB3QeGh9HMbcH9HQDEWlyZiFHwhThuAgc5gH3EYrtJoLRmAGkLXG0EJzTCZ2FYsMotI8x3BWA1RWEQxrc0oSsegktdwlWs4Fjy2MuWvKJo8eJPXFIHCejg2iV6vA7vQh5fHLuw+hyykIJlZtlHNWOTR6tE9HtHjB8xSKOvXWOAxXswYe+MY3s/fsox/rk7Y4AA/Kc9cbOinCwLxF6UHZ7kD2Wsyy6TJbF06SJxyKQOA4nJ7F3eB9VlxMev1t2QkhWX4oqZHGztE9xKrJcpY7u/ilTTAZKW/bE1RiJO9I7geeHT+FtuRCw/Oh2h9AlFyDJVXdKLDVUmhWc7suRTbzdweEiEYc7nz1xHfxhI2kSnCffmUV1bAypU9lOfXE0enrRlsusKa0hK1+M9yFVO0V1dBT9s+8aW9qT6IgYdMJGSp5/RwowjKWNLEpyF9RkB7XkTWeriQ55Sy+yKDuH0D8+a2z4wwIQTwuhSVwcm0HESmI7nWX95I6RI9CWYyqNY/IoiyCJi2OXO/EQk4utuTI+a35+vs3KqhOOKeCZPtnaQvbhA3ikEOfemEAsmZSo5I3ezeKr9eeoyb2RvHoNgUHZmmKjBaUD4hBDC0zHxfwmcmt/hd+dRf9QAjH52iTt7+2ZD5xqox/xie/KzhsxiROTOFpQYiiRv1fYwbONR6ghj9GBEcS/xssJ3sZ2Bt52N2bGryIR+W98xCQRU3O1UqlUWxNQh6w2HXrl5ke5jPzKCg7X1+WOyBqAUCKJqNwZPZOT5sKsyCtBYiHZiGNfPXXokye2WSsi/9UiDrMrKBd3jF0gPIiexCSiAzNwyD3BS8serNpTmXz6IBGv1qpifWcZ2/vrck3JHSZfc9FIAkPxNzDWPymXpV9ejXoHj7aKzUUyeHNzc20NXFdQnZJPxUAgYBzqSlDvVHZHqVRCVb4Z7CtOGxaAPOqRFJ9j8kOhEDwejzmX9MUgmfjJyYnBUxv604DJ0wIQQ4k6QXkluGAaH/HouyAfdfqtQVsuDmNRHO3Nv9K6LdQRewZHJYIQlA50p5Cvcl4wTFoLRxmDVAfkK3FMPAZHIh5xGbBiaME4Vz/kaSMG9dmTxzHjI5b6JTZt6cvOo402xTZ2ymSvRAGVlBiQBkWeBsTx63rkKRb1mCTn1GMjj8GpHuck9pRrUZRPv4yHRLnGpjyNi3MtDnXteelYMSknkU+b/wD4ldY7nz0NzAAAAABJRU5ErkJggg==
How to decode it to binary? I tried to remove the prefix data:image/png;base64, and use a base64 library to decode the rest into binary, and save it to a image.png file. But the file can't be displayed, with invalid format error.
But if I paste the whole string to http://base64online.org/decode/, it can play the image as well.
The base64 library I used is https://github.com/wstrange/base64, and my code is:
import 'package:base64_codec/base64_codec.dart';
var body = /* the long str above */;
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bs = Base64Codec.codec.decodeList(bStr.codeUnits);
var file = new File("image.png");
file.writeAsBytesSync(bs);
I don't know where is wrong :(
Try using the crypto package from the Dart SDK. This creates an image.png that opens fine (I believe it's this image... )
library foo;
import 'package:crypto/crypto.dart';
import 'dart:io';
void main() {
var body = "data:image/png;base64,iVBORw0KGgoAAAANSUh... snip";
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bytes = CryptoUtils.base64StringToBytes(bStr); // using CryptoUtils from Dart SDK
var file = new File("image.png");
file.writeAsBytesSync(bytes);
}
You'll need to add crypto to your pubspec.yaml :
dependencies:
crypto: any
The library linked at https://github.com/wstrange/base64, also notes in the README:
Deprecated
Note: The Dart SDK now includes a Base64 codec with url safe options. Unless you need streaming support, you should use the SDK methods as they are slightly faster.
See CryptoUtils
See also How to native convert string -> base64 and base64 -> string
Decoding as a string and then writing the bytes works:
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bs = Base64Codec.codec.decodeString(bStr);
var file = new File("image.png");
file.writeAsBytesSync(bs.codeUnits);
The easiest approach is:
import 'dart:convert';
final result = base64Decode(stringValue);

Force GetKeyNameText to english

The Win32 function GetKeyNameText will provide the name of keyboard keys in the current input locale.
From MSDN:
The key name is translated according to the layout of the currently
installed keyboard, thus the function may give different results for
different input locales.
Is it possible to force the input locale for a short amount of time? Or is there another alternative to GetKeyNameText that will always return the name in English?
Update: This answer does not work. It actually modifies the keyboard settings of the user. This appear to be a behavior change between Windows versions.
CString csLangId;
csLangId.Format( L"%08X", MAKELANGID( LANG_INVARIANT, SUBLANG_NEUTRAL ) );
HKL hLocale = LoadKeyboardLayout( (LPCTSTR)csLangId, KLF_ACTIVATE );
HKL hPrevious = ActivateKeyboardLayout( hLocale, KLF_SETFORPROCESS );
// Call GetKeyNameText
ActivateKeyboardLayout( hPrevious, KLF_SETFORPROCESS );
UnloadKeyboardLayout( hLocale );
WARNING: GetKeyNameText is broken (it returns wrong A-Z key names for non-english keyboard layouts since it uses MapVirtualKey with MAPVK_VK_TO_CHAR that is broken), keyboard layout dlls pKeyNames and pKeyNamesExt text is bugged and outdated. I cannot recommend dealing with this stuff at all. :)
If you're really-really want to get this info - then you can load and parse it manually from keyboard layout dll file (kbdus.dll, kbdger.dll etc).
There is a bunch of undocumented stuff involved:
In order to get proper keyboard layout dll file name first you need to convert HKL to KLID string. You can do this via such code:
// Returns KLID string of size KL_NAMELENGTH
// Same as GetKeyboardLayoutName but for any HKL
// https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values
BOOL GetKLIDFromHKL(HKL hkl, _Out_writes_(KL_NAMELENGTH) LPWSTR pwszKLID)
{
bool succeded = false;
if ((HIWORD(hkl) & 0xf000) == 0xf000) // deviceId contains layoutId
{
WORD layoutId = HIWORD(hkl) & 0x0fff;
HKEY key;
CHECK_EQ(::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key), ERROR_SUCCESS);
DWORD index = 0;
while (::RegEnumKeyW(key, index, pwszKLID, KL_NAMELENGTH) == ERROR_SUCCESS)
{
WCHAR layoutIdBuffer[MAX_PATH] = {};
DWORD layoutIdBufferSize = sizeof(layoutIdBuffer);
if (::RegGetValueW(key, pwszKLID, L"Layout Id", RRF_RT_REG_SZ, nullptr, layoutIdBuffer, &layoutIdBufferSize) == ERROR_SUCCESS)
{
if (layoutId == std::stoul(layoutIdBuffer, nullptr, 16))
{
succeded = true;
DBGPRINT("Found KLID 0x%ls by layoutId=0x%04x", pwszKLID, layoutId);
break;
}
}
++index;
}
CHECK_EQ(::RegCloseKey(key), ERROR_SUCCESS);
}
else
{
WORD langId = LOWORD(hkl);
// deviceId overrides langId if set
if (HIWORD(hkl) != 0)
langId = HIWORD(hkl);
std::swprintf(pwszKLID, KL_NAMELENGTH, L"%08X", langId);
succeded = true;
DBGPRINT("Found KLID 0x%ls by langId=0x%04x", pwszKLID, langId);
}
return succeded;
}
Then with KLID string you need to go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\%KLID% registry path and read Layout File string from it.
Load this dll file from SHGetKnownFolderPath(FOLDERID_System, ...) (usually C:\Windows\System32) with LoadLibrary() call.
Next you need to do GetProcAddress(KbdDllHandle, "KbdLayerDescriptor") - you're receive pointer that can be casted to PKBDTABLES.
There is kbd.h header in Windows SDK that have KBDTABLES struct definition (there is some stuff involved to use proper KBD_LONG_POINTER size for x32 code running on x64 Windows. See my link to Gtk source at the end).
You have to look at pKeyNames and pKeyNamesExt in it to get scan code -> key name mapping.
Long story short: The GTK toolkit have the code that doing all this(see here and here). Actually they are building scan code -> printed chars tables from Windows keyboard layout dlls.

Uninstall another MSI on install

I have a Basic MSI project. I need to remove another MSI product on installation that is now integrated into our main application. I tried to use the upgrade scenarios and treat it as a major upgrade. However, this didn't work because of the upgrade codes not matching I believe.
Next, I also made a custom action that ran msiexec.exe after the CostFinalize (I think this was stated in the Installshield help.) This worked perfectly until I installed on a system that didn't have the installer I was looking to remove. My installer would fail if the other obsolete product was not installed. I tried to put a condition on the custom action set by the system search, but it seems the system search is limited in functionality. I can't just check a reg key and set a boolean property.
Any ideas?
A few things to consider
1) The UpgradeTable ( FindRelatedProducts / RemoveExisting Products ) can be used to remove ProductCodes associated with another product's UpgradeCode.
2) If memory serves, MSI won't remove a Per-User product during a Per-Machine install ( or the other way around ). The context has to be the same.
3) The UI Sequence doesn't run during silent installs.
4) You can't run msiexec from the execute sequence because there is a system wide mutex of only one execute sequence per machine.
5) If you schedule in UI ( I already told you that you shouldn't since it doesn't run during silent installs ) there is another mutex that says only 1 UI per process.
If you are going from per-user to per-user or per-machine to per-machine, I would think it's reasonaable you should be able to do what you want using Upgrade elements / table rows without writing custom actions. Otherwise you'll need a setup.exe style bootstrapper to handle the uninstall prior to entering the msiexec world.
I achieved this in InstallShield 2013 using custom InstallScript. The script is executed via a custom action in the UI sequence, but I placed it after the "SetupProgress" dialog, i.e. before "Execute Action" instead of after the CostFinalize (as the documentation does say). I added the condition "NOT Installed" to the action. If you place this in the suggested order, it will kick off the uninstallation as soon as the installer has initialized. If you move it to where I did, it doesn't kick off until the user has clicked the final install now button.
The reason to put this in the UI sequence is to get around the one msi installer (or uninstaller) at a time problem. This simply doesn't work in the execution sequence because of that restriction.
The primary weakness in this method is that as Christopher stated, this won't work in a silent install (that is also found in the IS documentation). That is the official means for achieving this though. (check out: http://helpnet.installshield.com/installshield16helplib/IHelpCustomActionMSIExec.htm) If you can live with that (since silent install is generally as special case scenario), then this works just fine.
As Chris also said, you can't launch the uninstaller ui while the primary ui is running, but that's not a problem with my script because it adds a command line switch to run the uninstaller without the ui (i.e. silently).
My script also avoids having to know the guid of the application you want to uninstall. Here's the script to bind to the custom action (UninstallPriorVersions is the entry point function):
////////////////////////////////////////////////////////////////////////////////
//
// This template script provides the code necessary to build an entry-point
// function to be called in an InstallScript custom action.
//
//
// File Name: Setup.rul
//
// Description: InstallShield script
//
////////////////////////////////////////////////////////////////////////////////
// Include Ifx.h for built-in InstallScript function prototypes, for Windows
// Installer API function prototypes and constants, and to declare code for
// the OnBegin and OnEnd events.
#include "ifx.h"
// The keyword export identifies MyFunction() as an entry-point function.
// The argument it accepts must be a handle to the Installer database.
export prototype UninstallPriorVersions(HWND);
// To Do: Declare global variables, define constants, and prototype user-
// defined and DLL functions here.
prototype NUMBER UninstallApplicationByName( STRING );
prototype NUMBER GetUninstallCmdLine( STRING, BOOL, BYREF STRING );
prototype STRING GetUninstallKey( STRING );
prototype NUMBER RegDBGetSubKeyNameContainingValue( NUMBER, STRING, STRING, STRING, BYREF STRING );
// To Do: Create a custom action for this entry-point function:
// 1. Right-click on "Custom Actions" in the Sequences/Actions view.
// 2. Select "Custom Action Wizard" from the context menu.
// 3. Proceed through the wizard and give the custom action a unique name.
// 4. Select "Run InstallScript code" for the custom action type, and in
// the next panel select "MyFunction" (or the new name of the entry-
// point function) for the source.
// 5. Click Next, accepting the default selections until the wizard
// creates the custom action.
//
// Once you have made a custom action, you must execute it in your setup by
// inserting it into a sequence or making it the result of a dialog's
// control event.
///////////////////////////////////////////////////////////////////////////////
//
// Function: UninstallPriorVersions
//
// Purpose: Uninstall prior versions of this application
//
///////////////////////////////////////////////////////////////////////////////
function UninstallPriorVersions(hMSI)
begin
UninstallApplicationByName( "The Name Of Some App" );
end;
///////////////////////////////////////////////////////////////////////////////
//
// Function: UninstallApplicationByName
//
// Purpose: Uninstall an application (without knowing the guid)
//
// Returns: (UninstCmdLine is assigned a value by referrence)
// >= ISERR_SUCCESS The function successfully got the command line.
// < ISERR_SUCCESS The function failed to get the command line.
//
///////////////////////////////////////////////////////////////////////////////
function NUMBER UninstallApplicationByName( AppName )
NUMBER nReturn;
STRING UninstCmdLine;
begin
nReturn = GetUninstallCmdLine( AppName, TRUE, UninstCmdLine );
if( nReturn < ISERR_SUCCESS ) then
return nReturn;
endif;
if( LaunchAppAndWait( "", UninstCmdLine, LAAW_OPTION_WAIT) = 0 ) then
return ISERR_SUCCESS;
else
return ISERR_SUCCESS-1;
endif;
end;
///////////////////////////////////////////////////////////////////////////////
//
// Function: GetUninstallCmdLine
//
// Purpose: Get the command line statement to uninstall an application
//
// Returns: (UninstCmdLine is assigned a value by referrence)
// >= ISERR_SUCCESS The function successfully got the command line.
// < ISERR_SUCCESS The function failed to get the command line.
//
///////////////////////////////////////////////////////////////////////////////
function NUMBER GetUninstallCmdLine( AppName, Silent, UninstCmdLine )
NUMBER nReturn;
begin
nReturn = RegDBGetUninstCmdLine ( GetUninstallKey( AppName ), UninstCmdLine );
if( nReturn < ISERR_SUCCESS ) then
return nReturn;
endif;
if( Silent && StrFind( UninstCmdLine, "MsiExec.exe" ) >= 0 )then
UninstCmdLine = UninstCmdLine + " /qn";
endif;
return nReturn;
end;
///////////////////////////////////////////////////////////////////////////////
//
// Function: GetUninstallKey
//
// Purpose: Find the uninstall key in the registry for an application looked up by name
//
// Returns: The uninstall key (i.e. the guid or a fall back value)
//
///////////////////////////////////////////////////////////////////////////////
function STRING GetUninstallKey( AppName )
STRING guid;
STRING Key64, Key32, ValueName;
begin
Key64 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
Key32 = "SOFTWARE\\Wow6432Node\\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
ValueName = "DisplayName";
if( RegDBGetSubKeyNameContainingValue( HKEY_LOCAL_MACHINE, Key64, ValueName, AppName, guid ) = 0 ) then
return guid; // return 64 bit GUID
endif;
if( RegDBGetSubKeyNameContainingValue( HKEY_LOCAL_MACHINE, Key32, ValueName, AppName, guid ) = 0 ) then
return guid; // return 32 bit GUID
endif;
return AppName; // return old style uninstall key (fall back value)
end;
///////////////////////////////////////////////////////////////////////////////
//
// Function: RegDBGetSubKeyNameContainingValue
//
// Purpose: Find a registry sub key containing a given value.
// Return the NAME of the subkey (NOT the entire key path)
//
// Returns: (SubKeyName is assigned a value by referrence)
// = 0 A sub key name was found with a matching value
// != 0 Failed to find a sub key with a matching value
//
///////////////////////////////////////////////////////////////////////////////
function NUMBER RegDBGetSubKeyNameContainingValue( nRootKey, Key, ValueName, Value, SubKeyName )
STRING SearchSubKey, SubKey, svValue;
NUMBER nResult, nType, nvSize;
LIST listSubKeys;
begin
SubKeyName = "";
listSubKeys = ListCreate(STRINGLIST);
if (listSubKeys = LIST_NULL) then
MessageBox ("Unable to create necessary list.", SEVERE);
abort;
endif;
RegDBSetDefaultRoot( nRootKey );
if (RegDBQueryKey( Key, REGDB_KEYS, listSubKeys ) = 0) then
nResult = ListGetFirstString (listSubKeys, SubKey);
while (nResult != END_OF_LIST)
SearchSubKey = Key + "\\" + SubKey;
nType = REGDB_STRING;
if (RegDBGetKeyValueEx (SearchSubKey, ValueName, nType, svValue, nvSize) = 0) then
if( svValue = Value ) then
SubKeyName = SubKey;
nResult = END_OF_LIST;
endif;
endif;
if( nResult != END_OF_LIST ) then
nResult = ListGetNextString (listSubKeys, SubKey);
endif;
endwhile;
endif;
ListDestroy (listSubKeys );
if ( SubKeyName = "" ) then
return 1;
else
return 0;
endif;
end;

Resources