Hi!
In our company we want to deploy WDS to some desktops through Active Directory. For this we need an MSI file, however, the installer which can be downloaded from microsoft.com is available only as exe file. Does anyone have any information about how to turn it into a deployable MSI file Or is there a pre-built MSI available for download somewhere
Thanks
--Michael

WDS as deployable MSI?
papa99do
Hi Michael,
First, I’d like to stress that Microsoft doesn’t give any guarantees about a WDS installed from within an MSI wrapper not written by Microsoft. However, below are the steps that may help you create an MSI wrapper around the WDS setup .exe, assuming you know how to prepare a general MSI package. The sample code is provided “as is”, without any warranties. Error-checking in the code has been mostly omitted.
1) Author WDSSetup.exe into the Binary table of your MSI package. If you are editing the .wxs files directly you would add something like
<Binary Id=”WindowsDesktopSearch.exe” src=”path to WDSSetup.exe on the build machine” />
2) Author the command-line properties for WDSSetup.exe launch. E.g., to WDSSetup.exe in a quiet mode and suppress the potential post-setup restart, you would put the following in your .wxs file:
<Property Id=”WDSCOMMANDPARAM” Value=” "/quiet /norestart" />
3) In your custom action dll, add actions that stream out the WDS installer from your
MSI package and launch it. Below is some sample C++ code to achieve that:
#include <windows.h>
#include <Msiquery.h>
#include <TCHAR.h>
// A helper method to retrieve the necessary entries from the MSI's Property table
LPTSTR GetProperty(MSIHANDLE hInstall, LPCTSTR lpProperty, PDWORD pdwSize)
{
LPTSTR lpValue = NULL;
bool bRet = true;
DWORD dwSize=0;
UINT uResult = MsiGetProperty(hInstall, lpProperty, TEXT(""), &dwSize);
if (ERROR_MORE_DATA == uResult)
{
*pdwSize = ++dwSize;
lpValue = (LPTSTR) HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, dwSize*sizeof(TCHAR));
if (NULL != lpValue)
{
uResult = MsiGetProperty(hInstall, lpProperty, lpValue, &dwSize);
if (ERROR_SUCCESS != uResult)
{
HeapFree(GetProcessHeap(), 0, lpValue);
lpValue = NULL;
}
}
}
return lpValue;
}
// This routine streams out the WDS setup binary from the MSI package
UINT __stdcall StreamOutWDSPackage(MSIHANDLE hInstall)
{
UINT uRes = 0;
TCHAR szTempPath[MAX_PATH];
TCHAR szTempFile[MAX_PATH];
TCHAR szCommandLine[MAX_PATH];
HRESULT hr;
ZeroMemory(szTempPath, sizeof(szTempPath));
// get path to the temp directory
DWORD dwRes = GetTempPath(MAX_PATH, szTempPath);
ZeroMemory(szTempFile, sizeof(szTempFile));
// generate a fully qualified temp file name and with "wds" prefix
if (dwRes)
uRes = GetTempFileName(szTempPath, TEXT("wds"), 0, szTempFile);
if (!dwRes || !uRes)
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
//add .exe extension to the name of the temp file
hr = StringCchPrintf(szTempFile, MAX_PATH, TEXT("%s.exe"), szTempFile);
if (FAILED(hr))
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
// get the the windows desktop search setup command line parameters property
DWORD dwSize;
LPTSTR lpCommandParam = GetProperty(hInstall, TEXT("WDSCOMMANDPARAM"), &dwSize);
if (NULL == lpCommandParam)
return ERROR_INSTALL_FAILURE;
// put together the command line which includes the dir path, command name and parameters
ZeroMemory(szCommandLine, sizeof(szCommandLine));
hr = StringCchPrintf(szCommandLine, MAX_PATH, TEXT("\"%s\" %s"), szTempFile, lpCommandParam);
// free lpCommandParam since we don't need it anymore
if (lpCommandParam)
HeapFree(GetProcessHeap(), 0, lpCommandParam);
if (FAILED(hr))
return ERROR_INSTALL_FAILURE;
// create the actual temp file and get its handle. We'll use the handle to write data to
// this file.
HANDLE hFile = 0;
hFile = CreateFile(szTempFile, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
// Get handle to msi database
PMSIHANDLE hDB;
hDB = MsiGetActiveDatabase(hInstall);
if (NULL == hDB)
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
// prepare query to retrieve the WDS setup binary from the MSI package.
PMSIHANDLE hView;
if (ERROR_SUCCESS != MsiDatabaseOpenView(hDB,
TEXT("SELECT `Data` FROM `Binary` WHERE `Name` = 'WindowsDesktopSearch.exe'"),
&hView)
||
ERROR_SUCCESS != MsiViewExecute(hView, 0))
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
// Fetch data from query result
PMSIHANDLE hRec;
uRes = MsiViewFetch(hView, &hRec);
if (uRes != ERROR_SUCCESS)
{
// add code for logging the failure if needed
return ERROR_INSTALL_FAILURE;
}
// extract binary data from windows desktop search setup record
char szBuff[MAX_BUFFER*2];
DWORD dwBuffSize;
DWORD dwBytesWritten;
do {
// Extract binary data to buffer
dwBuffSize = MAX_BUFFER*2;
uRes = MsiRecordReadStream(hRec, 1, szBuff, &dwBuffSize);
if (ERROR_SUCCESS != uRes)
{
// add code for logging the failure if needed
break;
}
if(dwBuffSize)
{
// write the chunk of the binary data to disk
if ( !WriteFile(hFile, szBuff, dwBuffSize, &dwBytesWritten, NULL) )
{
// add code for logging the failure if needed
break;
}
}
}
while (dwBuffSize > 0);
CloseHandle(hFile);
// IMPORTANT! This will store the command line into the CustomActionData, to be
// later retrieved by the InstallWindowsDesktopSearch action
uRes = MsiSetProperty(hInstall, TEXT("InstallWindowsDesktopSearch"), szCommandLine);
if (ERROR_SUCCESS != uRes)
{
// add code for logging the failure if needed
}
return uRes;
}
// This routine retrieves the command line that will launch the WDS setup exe and launches an
// install helper
UINT __stdcall InstallWindowsDesktopSearch(MSIHANDLE hInstall)
{
UINT uRes = ERROR_SUCCESS;
DWORD dwSize;
LPTSTR lpCommand = GetProperty(hInstall, TEXT("CustomActionData"), &dwSize);
if ( lpCommand && dwSize > 0)
{
uRes = LaunchWDSSetup(hInstall, lpCommand);
}
else
uRes = ERROR_INSTALL_FAILURE;
HeapFree(GetProcessHeap(), 0, lpCommand);
return uRes;
}
// A helper that launches the WDS setup
UINT LaunchWDSSetup(MSIHANDLE hInstall, LPTSTR lpCommandLine)
{
STARTUPINFO startInfo;
ZeroMemory(&startInfo, sizeof(STARTUPINFO));
startInfo.cb = sizeof(STARTUPINFO);
startInfo.dwFlags |= STARTF_USESHOWWINDOW;
startInfo.wShowWindow = SW_SHOW; // or SW_HIDE, if you want a silent install
PROCESS_INFORMATION procInfo;
ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));
if(CreateProcess(NULL,
lpCommandLine,
NULL,
NULL,
TRUE, //inherit handles
CREATE_NO_WINDOW, // no window
NULL, //inherit environment
NULL, //inherit current dir
&startInfo,
&procInfo))
{
HANDLE hProcess = procInfo.hProcess;
WaitForSingleObject(hProcess, INFINITE);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
else
{
// Report error
}
return ERROR_SUCCESS;
}
4) Author the actions you added to your custom action dll into the Custom Action table.
(Also, author the custom action dll in to the Binary table if it’s not there yet.) Assuming your custom action dll is called ca.dll, it could look like this:
<Binary Id="CA.dll" src=”path to ca.dll” />
<CustomAction Id="StreamOutWDSPackage" BinaryKey="CA.dll" DllEntry="StreamOutWDSPackage" Return="ignore"/>
<CustomAction Id="InstallWindowsDesktopSearch" BinaryKey="CA.dll" DllEntry="InstallWindowsDesktopSearch" Execute="deferred"/>
Note that the execution of InstallWindowsDesktopSearch is deferred since it modifies the target system, so it will happen at the very end of the sequence. An upshot of it is that the InstallWindowsDesktopSearch has to get the command line for launching WDS setup through the CustomActionData private property (see the custom action code above).
5) Finally, appropriately schedule your custom actions into the Install Execute sequence.
The exact sequencing depends on what else you want your MSI to do, but here is an example:
<InstallExecuteSequence>
<LaunchConditions/>
<InstallInitialize/>
…
<Custom Action="StreamOutWDSPackage" After="InstallInitialize” />
<Custom Action="InstallWindowsDesktopSearch" After="StreamOutWDSPackage”/>
…
</InstallExecuteSequence>
Other things you might want your MSI to do include checking the presence of other WDS installations. WDS 2.6.5 should successfully upgrade from WDS 2.6. However, WDS 2.5 will have to be removed from the user’s machine before a “wrapped” WDS 2.6.5 install is attempted. The reason is that otherwise, WDS 2.6.5 itself will attempt to remove WDS 2.5. WDS 2.5 installer is an MSI package, so WDS 2.6.5 will launch Windows Installer to remove it. Since your WDS 2.6.5 installation is itself launched from an MSI wrapper, i.e. by Windows Installer, you will get an error saying that another installation is in progress.
Hope this helps,
Andrey Kolobov
MrRon
Are there any plans for Microsoft to go ahead and make an MSI version of WDS
Why wasn't this done to begin with
Also, you have any links to help us mere systems guys get started with the whole MSI thing