GPS Programming Tips for Windows Mobile - Part 1
NETCF: Memory leak... now what??
Supporting Kiosk-Applications on Windows Mobile (Technically achievable vs. supported)
Wireless Programming on Windows Mobile: supported or not supported?
Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes
Disable WebBrowser's Context-Menu in NETCF applications
MAPI on Windows Mobile 6: Programmatically retrieve mail BODY (sample code)
Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control
The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)
Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity
Support Boundaries for Windows Mobile Programming (Developing Drivers, for example... Or even WiFi Programming)
Miei post in italiano sul team-blog del Supporto Tecnico agli Sviluppatori
Recently I've worked with a developer on an interesting issue I’ve not found any clue on the web about, and the solution is based on one of those details that you can empirically retrieve but that there are not documented anywhere, therefore on future releases may change without any warning. This was for example what happened to the ClassName of NETCF applications... see Daniel Moth's post about this: "#NETCF_AGL_". I’ve also discussed about this in a MSDN Forum post I found interesting, where the topic was something like “how to prevent the CLR to not allow a second instance of the same NETCF application to run on Windows Mobile”. As I probably wrote elsewhere, “undocumented” doesn’t mean “technically not achievable”: it means that Product Group may change it as it doesn’t have to be backward-compatible.
In this case we had an application that may have been updated at a later time: the ISV was wondering if there’s any way in the setup.dll of application’s CAB to specify, during uninstallation, if the uninstall is taking place during a version-upgrade or if it's a pure uninstallation. This is because, for example, the application's installation copies also some large files that user no longer needs after the uninstall and therefore are deleted: but it still needs them if the user is uninstalling a former version of the app in order to install a newer one. I hope I've been clear... Things can get more complicated by the fact that the when you do an “upgrade” of the same ap
Well... we found out that there's no documented and standard way to achieve the goal, so we had to be creative - as usual... To understand how to operate, we needed to understand the actual flow when installing\uninstalling\upgrading (=installing the CAB of a newer version of the app while a older one is installed); moreover, we had to take care a particular condition, i.e. when upgrading the user is prompted with the message “The previous version of… Select Ok to continue or cancel to quit” -- and here it comes handy the “undocumented but empirically retrievable” info, that I'm going to show in a minute.
The regular flow when installing and uninstalling is:
When upgrading, the flow is as follows:
So the problem is how to let the installer know that it’s uninstalling or upgrading… the idea I had was to modify the flow this way, based on the fact that when “upgrading”, the flow involves firstly a Install_Init and secondly a Uninstall_Init; in contrast when “uninstalling” the flow doesn’t involve a first step through Install_Init:
a. Install
b. Upgrade:
c. Uninstall
To conclude, the idea was to:
HOWEVER… this approach had a problem… what happens if user answers “Cancel” to the prompt “The previous version of… Select Ok to continue or cancel to quit”? Nobody can restore [HKLM\UpgradeKey]Upgrade to 0 after that Install_Init set it to 1, and future possible “Uninstalls” are considered as “Upgrades”! So basically the problem is when user firstly doesn't accept to uninstall the previous version during upgrade and then secondly she uninstalls the previous version on her own: when doing this second action, the uninstall procedure would find that the Upgrade registry key is set to 1 and therefore would consider an upgrade even if in reality it's an uninstall.
So, next question was: is there any programmatic way to know if user selects “Cancel” when prompted about uninstalling previous version? The only way I could think at was to get ahold of the WCELOAD.EXE process and invoke GetExitCodeProcess() API to retrieve its return value: the assumption was that it was different when user hits “Cancel”… it turned out that this is true, but this approach involved an external application to be launched for example in setup.dll’s DLL_PROCESS_ATTACH, that can monitor WCELOAD.EXE and check its return value during Uninstall phase… Why an external process? Because the prompt comes up EVEN BEFORE the setup.dll can handle Install_Init.
The “undocumented but empirically retrievable” info I was mentioning at the beginning is precisely the return value of WCELOAD.EXE when user hits Cancel. As I said, not being documented it may change on future releases without any notice..
And now some code please!!
I’m talking about the following in setup.dll:
#define DELETE_STR(s) \ if (NULL != s) \ delete [] s; HINSTANCE g_hinstModule; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { //MessageBox(NULL, TEXT("Now attach the debugger"), TEXT("Test"), MB_OK); switch (ul_reason_for_call) { case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: g_hinstModule = (HINSTANCE)hModule; break; case DLL_PROCESS_ATTACH: g_hinstModule = (HINSTANCE)hModule; //check if UpgCheck.exe is already available on device (1st time it won't, but in any case we don't need it) LPCWSTR pszFileNameWithPath = new TCHAR[MAX_PATH]; pszFileNameWithPath = TEXT("\\Windows\\UpgCheck.exe"); WIN32_FIND_DATA wfdFindFileData; HANDLE hFile = FindFirstFile(pszFileNameWithPath, &wfdFindFileData); if(hFile == INVALID_HANDLE_VALUE) { DELETE_STR(pszFileNameWithPath); break; } FindClose(hFile); //Launch external process that will monitor wceload.exe BOOL bRet; SHELLEXECUTEINFO sei = {0}; sei.cbSize = sizeof(sei); sei.nShow = SW_SHOWNORMAL; sei.lpFile = pszFileNameWithPath; sei.lpParameters = TEXT(" "); bRet = ShellExecuteEx(&sei); //if (!bRet) // MessageBox(NULL, TEXT("Could not launch UpgCheck"), TEXT("Test"), MB_OK); DELETE_STR(pszFileNameWithPath); break; } return TRUE; }
#define DELETE_STR(s) \
if (NULL != s) \
delete [] s;
HINSTANCE g_hinstModule;
BOOL APIENTRY DllMain(
HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//MessageBox(NULL, TEXT("Now attach the debugger"), TEXT("Test"), MB_OK);
switch (ul_reason_for_call)
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
g_hinstModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_ATTACH:
//check if UpgCheck.exe is already available on device (1st time it won't, but in any case we don't need it)
LPCWSTR pszFileNameWithPath = new TCHAR[MAX_PATH];
pszFileNameWithPath = TEXT("\\Windows\\UpgCheck.exe");
WIN32_FIND_DATA wfdFindFileData;
HANDLE hFile = FindFirstFile(pszFileNameWithPath, &wfdFindFileData);
if(hFile == INVALID_HANDLE_VALUE)
DELETE_STR(pszFileNameWithPath);
}
FindClose(hFile);
//Launch external process that will monitor wceload.exe
BOOL bRet;
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.nShow = SW_SHOWNORMAL;
sei.lpFile = pszFileNameWithPath;
sei.lpParameters = TEXT(" ");
bRet = ShellExecuteEx(&sei);
//if (!bRet)
// MessageBox(NULL, TEXT("Could not launch UpgCheck"), TEXT("Test"), MB_OK);
return TRUE;
And I’m talking about something similar in the wceload-monitor:
int _tmain(int argc, _TCHAR* argv[]) { int const MAXBUF = 32; HRESULT hr = E_FAIL; HANDLE hProcess = NULL; BOOL bRes = FALSE; DWORD dwRes = 0; LPTSTR lpBuf = new TCHAR[MAXBUF]; ZeroMemory(lpBuf, MAXBUF - 1); //retrieve process handle of wceload.exe, until it's found do{ hr = GetProcessHandleByName(TEXT("wceload.exe"), &hProcess); CHR(hr); Sleep(1000); } while (INVALID_HANDLE_VALUE == hProcess); //hr = LogToFile(TEXT("\r\nwceload found!\r\n"), g_pszFilename); //CHR(hr); //retrieve wceload.exe exit code, until it exits do { Sleep(1000); bRes = GetExitCodeProcess(hProcess, &dwRes); if ( !bRes ) { goto Exit; //GetLastError } } while (STILL_ACTIVE == dwRes); hr = StringCchPrintf(lpBuf, LocalSize(lpBuf) / sizeof(TCHAR), TEXT("ExitCode %d"), dwRes); //2147754005 when user select Cancel (0x80042015) CHR(hr); hr = LogToFile(lpBuf, g_pszFilename); CHR(hr); //success hr = S_OK; Exit: DELETE_STR(lpBuf); return 0; }
int _tmain(int argc, _TCHAR* argv[])
int const MAXBUF = 32;
HRESULT hr = E_FAIL;
HANDLE hProcess = NULL;
BOOL bRes = FALSE;
DWORD dwRes = 0;
LPTSTR lpBuf = new TCHAR[MAXBUF];
ZeroMemory(lpBuf, MAXBUF - 1);
//retrieve process handle of wceload.exe, until it's found
do{
hr = GetProcessHandleByName(TEXT("wceload.exe"), &hProcess);
CHR(hr);
Sleep(1000);
} while (INVALID_HANDLE_VALUE == hProcess);
//hr = LogToFile(TEXT("\r\nwceload found!\r\n"), g_pszFilename);
//CHR(hr);
//retrieve wceload.exe exit code, until it exits
do {
bRes = GetExitCodeProcess(hProcess, &dwRes);
if ( !bRes )
goto Exit; //GetLastError
} while (STILL_ACTIVE == dwRes);
hr = StringCchPrintf(lpBuf,
LocalSize(lpBuf) / sizeof(TCHAR),
TEXT("ExitCode %d"),
dwRes); //2147754005 when user select Cancel (0x80042015)
hr = LogToFile(lpBuf, g_pszFilename);
//success
hr = S_OK;
Exit:
DELETE_STR(lpBuf);
return 0;
Where the helper functions are:
// ************************************************************************** // Function Name: GetProcessHandleByName HRESULT GetProcessHandleByName (LPCTSTR pszProcessName, LPHANDLE phProcessHandle) { HRESULT hr = E_FAIL; if (pszProcessName == NULL) goto Exit; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) goto Exit; *phProcessHandle = NULL; PROCESSENTRY32 pe; pe.dwSize = sizeof(pe); if (Process32First(hSnapshot, &pe)) { do { //log Exe name hr = LogToFile(pe.szExeFile, g_pszFilename); CHR(hr); hr = LogToFile(TEXT("\r\n"), g_pszFilename); CHR(hr); //compare current Exe name with passed Process Name if (lstrcmpi(pszProcessName, pe.szExeFile) == 0) { //get the handle of the Exe name in case we reached the Exe we were looking for *phProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); CloseHandle(hSnapshot); return TRUE; } } while (Process32Next(hSnapshot, &pe)); } //Success hr = S_OK; Exit: if (NULL != hSnapshot) //UPDATE: thanks Vino! //Contrarily to desktop Win32, don't invoke CloseHandle() to close the snapshot call. //Desktop (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx): // "[...] To destroy the snapshot, use the CloseHandle function.". //Windows CE\Mobile (http://msdn.microsoft.com/en-us/library/aa911386.aspx): // "[...] To close a snapshot, call the CloseToolhelp32Snapshot function." CloseToolhelp32Snapshot(hSnapshot); return hr; } // ************************************************************************** // Function Name: LogToFile HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename) { HRESULT hr = E_FAIL; //Open the handle to the file (and create it if it doesn't exist HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) goto Exit; //Set the pointer at the end so that we can append szLog DWORD dwFilePointer = SetFilePointer(hFile, 0, NULL, FILE_END); if (0xFFFFFFFF == dwFilePointer) goto Exit; //Write to the file DWORD dwBytesWritten = 0; BOOL bWriteFileRet = WriteFile(hFile, szLog, wcslen(szLog) * 2, &dwBytesWritten, NULL); if (!bWriteFileRet) goto Exit; //Flush the buffer BOOL bFlushFileBuffersRet = FlushFileBuffers(hFile); if (!bFlushFileBuffersRet) goto Exit; //Success hr = S_OK; Exit: if (NULL != hFile) CloseHandle(hFile); return hr; }
// **************************************************************************
// Function Name: GetProcessHandleByName
HRESULT GetProcessHandleByName (LPCTSTR pszProcessName, LPHANDLE phProcessHandle)
if (pszProcessName == NULL)
goto Exit;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
*phProcessHandle = NULL;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(hSnapshot, &pe))
//log Exe name
hr = LogToFile(pe.szExeFile, g_pszFilename);
hr = LogToFile(TEXT("\r\n"), g_pszFilename);
//compare current Exe name with passed Process Name
if (lstrcmpi(pszProcessName, pe.szExeFile) == 0)
//get the handle of the Exe name in case we reached the Exe we were looking for
*phProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
CloseHandle(hSnapshot);
} while (Process32Next(hSnapshot, &pe));
//Success
if (NULL != hSnapshot)
//UPDATE: thanks Vino!
//Contrarily to desktop Win32, don't invoke CloseHandle() to close the snapshot call.
//Desktop (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx):
// "[...] To destroy the snapshot, use the CloseHandle function.".
//Windows CE\Mobile (http://msdn.microsoft.com/en-us/library/aa911386.aspx):
// "[...] To close a snapshot, call the CloseToolhelp32Snapshot function."
CloseToolhelp32Snapshot(hSnapshot);
return hr;
// Function Name: LogToFile
HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename)
//Open the handle to the file (and create it if it doesn't exist
HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
//Set the pointer at the end so that we can append szLog
DWORD dwFilePointer = SetFilePointer(hFile, 0, NULL, FILE_END);
if (0xFFFFFFFF == dwFilePointer)
//Write to the file
DWORD dwBytesWritten = 0;
BOOL bWriteFileRet = WriteFile(hFile, szLog, wcslen(szLog) * 2, &dwBytesWritten, NULL);
if (!bWriteFileRet)
//Flush the buffer
BOOL bFlushFileBuffersRet = FlushFileBuffers(hFile);
if (!bFlushFileBuffersRet)
if (NULL != hFile)
CloseHandle(hFile);
Hope this can help someone that absolutely has to distinguish if the application needs to be uninstalled or upgraded… but maybe the code above can find other meaningful usage!
Cheers,
~raffaele
PingBack from http://microsoft-sharepoint.simplynetdev.com/programmatically-discriminate-between-upgrade-or-uninstall-of-a-cab-on-windows-mobile/
Very interesting post. I have had a similar problem and maybe it's interesting to know that OpenProcess (you use this API in GetProcessHandleByName) could fail and return NULL on Smartphone with two-tier Lock or Prompt security policy. I was calling it with SYNCHRONIZE instead of PROCESS_ALL_ACCESS, but it should not make any difference, since these flags are not supported in Windows CE 5.x.
My solution was to cast the process id pe.th32ProcessID to a handle , since process ID and process handle are the same in Window CE (I'm not sure it's documented, but it is true, at least up to CE 5):
*phProcessHandle = (HANDLE)pe.th32ProcessID;
This seems to work fine with the most common Smartphone security policies.
Giuseppe
Grazie Giuseppe! :-)
In the sample code, in the function GetProcessHandleByName(), shouldn't
actually be
-Vino
Thanks for commenting!
And thanks for the hint... I was used to the DESKTOP-way, where CloseHandle is fine: see http://msdn.microsoft.com/en-us/library/ms686701(VS.85).aspx, for example, or even in the CreateToolhelp32Snapshot doc (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx) it's reported "[...] To destroy the snapshot, use the CloseHandle function.".
But you're totally correct: on Windows CE the story is different... see the doc page for CreateToolhelp32Snapshot (http://msdn.microsoft.com/en-us/library/aa911386.aspx) "[...] To close a snapshot, call the CloseToolhelp32Snapshot function. Do not call the CloseHandle function to close the snapshot call. That generates a memory leak. "
So thanks again and please continue validating my code!! :-)
Apart from the code, I was more interested in getting rid of the message "“The previous version of… Select Ok to continue or cancel to quit”".
We have an app packaged as a cab file. This cab has been created using the /nouninstall flag. This means the app will not appear in the 'Remove Applications' section. Also, we have our own uninstall logic built into the app. Also, we do not write into the registry whatsoever. Further, if we uninstall and the re-install the app, everything works fine with no prompts from wceload. This has worked fine with WM 6.0
Come WM 6.1, using the same cab exhibits different behavior. After uninstalling the app, and then installing it again, we get the above message. I select OK. Moving on, another prompt comes up saying, ' The app cannot be removed. Proceed?'. Select Yes, and the cab extracts successfully. But at the end of it, yet another prompt which says, 'The app.cab was not installed successfully.' All this prompts and our customers are very unhappy.
My question is there anyway I can get rid of the prompts?
I searched the web and found http://social.msdn.microsoft.com/Forums/en-US/vssmartdevicesvbcs/thread/835784af-def1-4928-af6f-e7d8d326aa5c
But it did not help at all. I guess the issue is still open.
Any help/advice/suggestion is appreciated.
hmm... I would say that your query deserves some tests and researches. Why not opening a call towards Microsoft Technical Support? If you're located in EMEA, we may end up talking each other... :-) Remember: "get what you paid for"!! (http://blogs.msdn.com/raffael/archive/2008/05/14/get-what-you-paid-for.aspx)
There is one interesting workaround for this problem. It worked for WM 5.0-6.0, but it doesn't work for 6.1 and actually I am looking a way of fixing it.
The idea is to give different names to your application in the .inf file
Something like:
Cab N1:
AppName = "FooBar 1.0"
Cab N2 (for upgrade)
AppName = "FooBar 1.1"
Now, you can install/uninstall.
In order to determine upgrade case, you can check in Setup.dll in Install_Init that there is registry key HKLM\\Software\\App\\FooBar <version>
And actually in this case, you can do REAL upgrade with replacing some files, instead of uninstallation + installation.
The problem is that now you need programmatically cleanup some registry and files to remove old entry from "Remove programs" menu.
It was enough before 6.1 to remove registry entries
HKLM\\Security\\Apps\\AppName
HKLM\\Software\\Apps\\AppName
and director
\windows\appmgr\AppName\
However, WM 6.1 saves app name at some other location too. And I can't figure out where it is.
Hi Victor, I would say you were using a unsupported way to remove entries under the "Remove Programs" list (removing registry keys), contrarily to using the UnInstall CSP, for example.
Anyway, you may find interesting a freeware tool from SK Tools called "ssnap", which takes a snap-shot of the state of PPC that is useful for monitoring of changes on PPC, e.g. those made during installation of programs (http://www.s-k-tools.com/index.html?m_util.html). I've never used it but can be useful if you want to pursue with this approach...
HTH, bye!
I used the MemMaid tool and I found out that after an installation, the application entry is made into the mxip_swmgmt.vol.
Is there any way to get rid of this entry especially considering that the cab was created using the /nouninstall flag ? Here is the relevant post with what I have tried - http://forum.xda-developers.com/showthread.php?p=3769269
Thanks for the Post!
I implemented your solution and it seems to be working fine for most part. Only thing is that during an "upgrade", it takes a really really long time to actually install the app after clicking on "OK".
I put in alerts and it looks like it takes a long time to move from uninstall_init to uninstall_exit (even if the uninstall methods do not do anything).
My first hunch was that it takes this long since multiple wceload monitor processes are getting spawned after selecting "OK", so I tried making the monitor a single instance. That did not help reduce the time and for some reason the "Cancel" part stopped working as expected!:-(
Any idea on how to speed up the install? It really feels like the install is hung (and it doesn't show the wait cursor either:-( )
Also, in case of an SD card, if the user decides to go ahead with the upgrade (i.e. clicks "OK") and then bails out on the screen which asks whether to install the app to Main Memory or the SD Card, the application is already uninstalled at this point. Is there a way to handle this case?
Thanks again!
Thank you very much for providing a flow on how the CAB installation process works. I had found out before, but that was by trial and error, so this is a great post that I wished I had read 6 months ago. :)
hmm, am I missing something or API has been changed since, but...
SETUP_API codeINSTALL_INIT
Install_Init(
HWND hwndParent,
BOOL firstCall, // is this the first time this function is being called?
BOOL previouslyInstalled,
LPCTSTR installDir
... has "BOOL previouslyInstalled" parameter