Sometimes there’s a problem with saving Excel files on Windows 7 machines in the enterprise. If you haven’t seen this problem yet, count yourself fortunate. The problem is, there are a couple of different flavors of this problem and both are frustrating. I’m talking about what happens when you try to save an Excel 2007 or 2010 document when you’re working on a Windows 7 machine. You may get an error indicating the document wasn’t saved or that someone else is using the file and can’t be saved. Let’s break this problem down into several yummy flavors:
“Document not saved” – When this one happens, the error is consistent. This is a problem that can happen when you try to save to a location on your machine that is redirected to a network location, like your desktop. The issue won’t happen if you save to a local directory on the machine. Even more bizarre, the problem only affects Excel – not Word or PowerPoint. Only Excel. Upon closer examination, a debug revealed the server was throwing a “Quota_Exceeded” error back at Excel and denying the save. But this can happen even when quotas aren’t enabled on the server. Say WHAT? Yep, it’s true. While quotas may be the cause of the problem in some cases, this can affect a server that specifically has no quotas set. If your server is Small Business Server you will want to check quotas as they are set by default. You have to actually go in and disable them for quotas not to be enabled out of the box. But let’s assume quotas aren’t the problem. Take a look at the size of your desktop folder on the server. A folder that measures in gigabytes should send up red flags for obvious reasons. For one, Windows has to load that big folder before it can display your desktop on startup. And if it has to slog through gigabytes of information then the startup time is going to be slow. It’s recommended that you get rid of the unnecessary items from the desktop by moving them elsewhere or by deleting them. This problem has been confirmed on a desktop running over 6GB in files.
“Document not saved” – Yet another flavor of the ‘document not saved’ error. In this particular case, the error happens when some of the following conditions are true:
This ‘document not saved’ problem happens when two or more people save changes to the Excel file at the same time. The only known workaround for this problem is an add-in which prevents saving the document at the same time. In order to acquire the add-in, which isn’t approved for release as yet, contact Microsoft Support.
“Your changes could not be saved to <Document Name> because of a sharing violation. Try saving to a different file” – This problem appears to happen sporadically when it manifests. This one may also be followed by the following errors: “The file you are trying to open, <Random Characters>, is in a different format than specified by the file extension. Verify that the file is not corrupted and is from a trusted source before opening the file. Do you want to open the file now?" This problem can occur if you repeatedly save the workbook at short intervals. For this problem, there is a fix available in Microsoft Knowledge Base article 982125 (http://support.microsoft.com/kb/982125).
Failure to save files from Windows 7 or Windows Server 2008 R2 clients – This particular version of the file save problem is covered under Microsoft KB Article 234932 (http://support.microsoft.com/kb/2434932) and includes a hotfix. This problem only affects clients running Windows 7 or Windows 2008 R2 when you map a share to a server, make the share an offline location and then save files to that share.
In this problem, the temporary files that get created aren’t synchronizing correctly to the server share. What’s worse, the original files on the share are deleted and replaced with damaged TMP files. You don’t even receive a notification that the files were deleted, though you will receive a conflict notification that the files are missing from the server after you restart the server. This problem doesn’t affect files saved on the local machine.
When you receive this error in Microsoft Excel, it would seem to be pretty self explanatory. Excel cannot insert the sheets into the destination workbook, because it contains fewer rows and columns than the source workbook. To move or copy the data to the destination workbook, you can select the data, and then use Copy and Paste commands to insert it into the sheets of another workbook.
Excel 2003 and earlier spreadsheets (XLS file extension) has a max of 65,536 rows and 256 columns. With Excel 2007 and 2010 you now have the metro file format (XLSX, XLSM, etc.) which is capable of handling 1,048,576 rows and 16,384 columns of data. This error appears when you try to copy or move a metro file sheet to a non-metro (XLS) sheet. Basically you’re trying to fit a much larger page into a small book and still expect the sizes to be equal. Well, Excel doesn’t like that. In fact, it says “Uh, no” when you try to do this with a spreadsheet. That’s all well and good. But what about when you get this error running VBA code that used to work without a problem in Excel 2003? To answer this question you might want to take a look at your code. If you use a line of code with the .Move method this may well be your problem and here is why: if you are creating a separate workbook on the fly during your code operation, maybe for concatenating or manipulating data, and then later attempting to move a sheet from the newly created workbook into the legacy 2003 and earlier XLS file from which you are running the code, then you’re going to get this error. Why? When you run the code from Excel 2003 or earlier, any new workbooks created while running your code are also of the legacy format. But when you run the same macro in Excel 2007 or 2010 from the XLS file, you are creating a new XLSX metro workbook type and then telling Excel to stuff the larger page into the smaller one by issuing the move or copy command. Fine, so how do we work around this situation? There are a couple of ways. First, you can open the XLS file that contains the macro and click File > Save As before you run the macro. Save the file as an XLSM file type (metro file with a macro). Then when running the macro, you are working two equally–sized workbooks. You can then do another “Save As” to save the file back to the XLS legacy file format. The only drawback here is if you exceed the allowable rows and/or columns for legacy files.
Another workaround would be to use a copy and paste instead of a move or copy to copy the data that you need and paste that data into the XLS spreadsheet. Of course there are a couple of caviats with this solution. First, if you’re copying more than 65,536 rows of data or more than 256 columns of information then you won’t be able to paste it into the XLS file. You would get the same error message. Second, the code to copy and paste only the data you need is more involved than a one-line ‘Sheets.Move’ statement.
Hopefully, this will shed some light on why you might be encountering this error in VBA code. Let me know if you have any questions!
Here’s the problem. On a Windows 7 or Windows 2008 Server R2 machine, if you save your Excel 2010 documents into a My Documents (or subfolder like My Pictures) that has been re-mapped to a network location you may notice that it takes almost 40 seconds for the save to complete. The behavior is not consistent, however, as the latency does not occur every time you hit the save button. Fortunately, there is a fix for this problem. Apparently this was a known issue with Windows 7 and Windows 2008 R2, but the KB article to which this applies doesn't give any indication of the symptom, which is why I never found it. The hotfix contains an update for a Windows 7 networking driver which is causing the problem. The technical explanation: Excel 2010 makes an API call to Windows via a background thread which is waiting for the network driver to respond, which doesn't happen. The thread then times out and the save operation completes.
Solution:
1. Go to http://support.microsoft.com/kb/981711 – yes, that is the right KB article, even though the title talks about a stop error. Click the 'View and Request Hotfix Downloads' link at the top. Important: If you are downloading this hotfix for a computer other than the one you are viewing the article from then pay close attention to the link next to Step 1 on that page - the page uses an auto-detection script to determine if you are on x86, x64 or I64 systems. If you are wanting to apply this fix to an x86 machine and you are downloading from an x64 machine the hotfix you will receive by default is an x64 hotfix. If you click that link it will show all three hotfix types available. If you aren't sure which kind of machine you need it or, click the Start button and right click 'Computer' and left click on 'Properties'. Under 'System' the 'System type' will tell you what kind of operating system you are running.
2. Once you have the hotfix for the machine, just double click it. The exe file that you download will extract an MSU file to a directory you specify. After that's done, double click the extracted .MSU file to run it. A system reboot WILL be necessary in most cases. After the hotfix is applied you should no longer see the save problems. I applied the hotfix to the test machine I had set up and the problem did go away.
This is an interesting error. If you own Corel Paint Shop Pro X2 you get prompted to install updates. In this case, the update fails to install and throws this nice dialog – “Error 1920.Service.ProtexisLicensing (ProtexisLicensing) failed to start. Verify that you have sufficient privileges to start system services.”
As you might have guessed, this problem doesn’t have anything to do with privileges. However it does have to do with the ProtexisLicensing service. If you open up the Services snap-in and look for this service you’re likely to find the ProtexisLicensing service – in the STARTING state.
That’s a problem. Starting means the service is neither started nor stopped. In fact, if you try to stop or restart this service you get an error that the service is currently busy.
The way to get past this error is to open Task Manager (START, RUN and type TASKMGR and hit ENTER) and click the Processes tab. Find PSIService.exe in the list. Left click it once to highlight it and then click the End Process button. This will kill the PSIService and the ProtexisLicensing service will be in the stopped state. Now you can go back to the Services snap-in and start the service manually. After it has started, you can click the RETRY button in the error dialog and the update should finish.
This post explains in detail how to embed a manifest file into an executable (EXE) file. Embedding manifest files to EXEs is a requirement for the 'Certififed for Windows Vista' program (http://download.microsoft.com/download/8/e/4/8e4c929d-679a-4238-8c21-2dcc8ed1f35c/Windows%20Vista%20Software%20Logo%20Spec%201.1.doc)For the purposes of this article, the term 'appname' refers to a sample application and should be replaced with the name of your application.1. In Visual Studio 2005, open your application and go to the configuration manager. If you have x86 there already, click the drop down next to it and click <Edit…>. Find x86 in the list, select it and click Remove, the click Close. Now under Platform, click the drop down and select <New…>. Under New Platform, click the drop down and select x86. Under ‘Copy Settings From’, click the drop down and select <Empty>. Make sure the ‘Create New Solution Platforms’ checkbox is NOT checked. Click OK and Close to exit the configuration manager. Note: If you are using a post-build script to call MT.exe remove it as we will not need to use MT.exe to embed the manifest file.2. Rebuild the application.3. In VS 2005, click File, Close Solution. 4. Open notepad and paste the following into the new document:<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="asInvoker" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo></assembly>5. Click File, Save As and save this file as appname.exe.manifest where ‘appname’ is the name of the application you built in Step 1. Close the file.6. In VS 2005, click File, Open, File and browse to the location of your bin directory or where your EXE output is built and open appname.exe7. In the main Visual Studio 2005 window you should see a treeview with appname.exe – expand that by clicking the plus (+) sign. Underneath you should have at least two nodes for ‘Icon’ and ‘Version’. Right click appname.exe and left click Add Resource. Click the Import button and browse to where you saved the file from Step 5 (the notepad file you named as appname.exe.manifest) and select it and click Open. You may need to change the Files of Type to All Files in order to see it. A ‘Custom Resource Type’ dialogue appears asking for a resource type – type RT_MANIFEST and click OK. This will add the manifest with a value of 101. Right click that manifest file and left click on Properties. Change the ID property from 101 to 1.NOTE: If you want to verify the XML in the manifest is correct, double click the manifest in the tree view to see the binary. The ASCII data will be on the far right side.8. Click File, Save and close the file. 9. If the .EXE file(s) you are working with is part of a ClickOnce deployment project you will need to update the ClickOnce application manifest and deployment manifest files since embedding a manifest to the executable file will change the hash of the file you are working with. You can use MageUI or Mage to do this. If using Mage.exe, open a command prompt and navigate to the directory where Mage.exe is stored and execute two commands:mage.exe -u <path to appname.exe.manifest file>\appname.exe.manifest -Name "AppName" -Version 1.0.0.0 -FromDirectory <path to where the appname.exe.manifest exists> -cf <path to the .pfx file>\name.pfx -pwd "password if you used one"where 1.0.0.0 is the version of your applicationmage.exe -u <path to .application file>\appname.application -appm <path to appname.exe.manifest>\appname.exe.manifest -cf <path to the .pfx file>\name.pfx -pwd "password if you used one"10. You are finished embedding the manifest to the .EXE file.
Recently this question was asked of me. How to do a fade-in effect for audio files. I assumed media player had a built in function to cover fading audio in and out when it starts. I looked, looked some more, looked harder and finally gave up. There didn't seem to be a way to do it. So I came up with my own way.This code sample increments the volume over time in increments of half a second. You need only paste the code into an HTML page and change the line Player.URL = "Goldfish.avi"; to point to your media content. Enjoy!
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML XMLNS:t="urn:schemas-microsoft-com:time"><HEAD><TITLE>volume / mute Property</TITLE><?IMPORT namespace="t" implementation="#default#time2"> <STYLE> .time{ behavior: url(#default#time2);}</STYLE><script type="text/javascript" language="JavaScript"><!-- This code accomplishes the delay functionality --> /** constructor @param duration integer seconds @param <optional> function to run while waiting. */ function Pause(duration, busy){ this.duration= duration * 1000; this.busywork = null; // function to call while waiting. this.runner = 0; if (arguments.length == 2) { this.busywork = busy; } this.pause(this.duration); } // Pause class /** pause method @param duration: integer in seconds */ Pause.prototype.pause = function(duration){ if ( (duration == null) || (duration < 0)) {return;} var later = (new Date()).getTime() + duration; while(true){ if ((new Date()).getTime() > later) { break; } this.runner++; if (this.busywork != null) { this.busywork(this.runner); } } // while } // pause method function Play() {<!-- This function increases the volume with a delay of .5 seconds between each increase --> if (Player.playstate == 3){ for (i = 0; i < 10; i++) { var p = new Pause(.5); Player.settings.volume = Player.settings.volume + 5; } } else { alert("Not hit"); } alert(Player.settings.volume); }function go(){<!-- This code starts the embedded player playing. Substitute the name of your media file in the --><!-- first line below --> Player.URL = "Goldfish.avi"; Player.controls.Play(); return;}Player.on</SCRIPT> </HEAD><BODY><P><OBJECT ID="Player" width="320" height="300" CLASSID="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6"><PARAM name = "Volume" value="1"> <PARAM name = "AutoStart" value="-1"><param name="URL" value=""><param name="rate" value="1"><param name="balance" value="0"><param name="currentPosition" value="0"><param name="defaultFrame" value=""><param name="playCount" value="1"><param name="currentMarker" value="0"><param name="invokeURLs" value="-1"><param name="baseURL" value=""><param name="mute" value="0"><param name="uiMode" value="full"><param name="stretchToFit" value="0"><param name="windowlessVideo" value="0"><param name="enabled" value="-1"><param name="enableContextMenu" value="-1"><param name="fullScreen" value="0"><param name="SAMIStyle" value=""><param name="SAMILang" value=""><param name="SAMIFilename" value=""><param name="captioningID" value=""><param name="enableErrorDialogs" value="0"></OBJECT> <SCRIPT LANGUAGE = "JScript" FOR = "Player" EVENT = "PlayStateChange(NewState);"> if(3 == NewState) { Play(); }</SCRIPT><BR><BR><INPUT TYPE="BUTTON" NAME="BtnPlay" VALUE="Play" accesskey=p OnClick="go()"> </BODY></HTML>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML XMLNS:t="urn:schemas-microsoft-com:time"><HEAD><TITLE>volume / mute Property</TITLE><?IMPORT namespace="t" implementation="#default#time2">
<STYLE> .time{ behavior: url(#default#time2);}</STYLE><script type="text/javascript" language="JavaScript"><!-- This code accomplishes the delay functionality --> /** constructor @param duration integer seconds @param <optional> function to run while waiting. */ function Pause(duration, busy){ this.duration= duration * 1000; this.busywork = null; // function to call while waiting. this.runner = 0;
if (arguments.length == 2) { this.busywork = busy; }
this.pause(this.duration);
} // Pause class
/** pause method @param duration: integer in seconds */ Pause.prototype.pause = function(duration){ if ( (duration == null) || (duration < 0)) {return;}
var later = (new Date()).getTime() + duration;
while(true){ if ((new Date()).getTime() > later) { break; }
this.runner++;
if (this.busywork != null) { this.busywork(this.runner); }
} // while
} // pause method
function Play() {<!-- This function increases the volume with a delay of .5 seconds between each increase --> if (Player.playstate == 3){ for (i = 0; i < 10; i++) { var p = new Pause(.5); Player.settings.volume = Player.settings.volume + 5; } } else { alert("Not hit"); } alert(Player.settings.volume); }function go(){<!-- This code starts the embedded player playing. Substitute the name of your media file in the --><!-- first line below -->
Player.URL = "Goldfish.avi"; Player.controls.Play(); return;}Player.on</SCRIPT>
</HEAD><BODY><P><OBJECT ID="Player" width="320" height="300" CLASSID="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6"><PARAM name = "Volume" value="1"> <PARAM name = "AutoStart" value="-1"><param name="URL" value=""><param name="rate" value="1"><param name="balance" value="0"><param name="currentPosition" value="0"><param name="defaultFrame" value=""><param name="playCount" value="1"><param name="currentMarker" value="0"><param name="invokeURLs" value="-1"><param name="baseURL" value=""><param name="mute" value="0"><param name="uiMode" value="full"><param name="stretchToFit" value="0"><param name="windowlessVideo" value="0"><param name="enabled" value="-1"><param name="enableContextMenu" value="-1"><param name="fullScreen" value="0"><param name="SAMIStyle" value=""><param name="SAMILang" value=""><param name="SAMIFilename" value=""><param name="captioningID" value=""><param name="enableErrorDialogs" value="0"></OBJECT> <SCRIPT LANGUAGE = "JScript" FOR = "Player" EVENT = "PlayStateChange(NewState);"> if(3 == NewState) { Play(); }</SCRIPT><BR><BR><INPUT TYPE="BUTTON" NAME="BtnPlay" VALUE="Play" accesskey=p OnClick="go()">
</BODY></HTML>
If you've ever written VB.NET code and you've tried your hand at creating a C++ app then you probably know the frustration of how hard it is to get a simple C++ project running and working. Oh sure, 'Hello World' isn't so bad (partially because it can be pre-built for you in Visual Studio) but try to implement a function out of an API and your simple half hour project could easily turn into a two day project.
The purpose of this blog is for those starting C++ in Visual Studio .NET 2005. For the purpose of this post, I'll include sample code that I wrote which makes use of msienumproducts from the Windows Installer API. This function will return a list, or enumerate, products installed by the Windows Installer on your machine. First, take a look at the function descriptor on this MSDN page: http://msdn2.microsoft.com/en-us/library/aa370101.aspx - we see the following to start:
UINT MsiEnumProducts( DWORD iProductIndex, LPTSTR lpProductBuf );Here we see the declarator for MsiEnumProducts is of type UINT, an unsigned integer. That means when this function runs, it's going to return an integer value that will signify one of five things. The MSDN page does tell you what those five things are, but not their corresponding integers, so I'll list them below:
This function takes two arguments to work properly. The first is iProductIndex, declared as a DWORD, and lpProductBuf, declared as a LPTSTR which is a typedef. Not providing these in your code will product a compilation error.
Okay, so now that we know what the function is all about we can just slap it into our code and assign variables to it, right? Well, not exactly. Remember, C++ gives the user 'ultimate' control over the environment, which also means a lot more work needs to be done to make simple things work - the same things VB programmers could easily take for granted. The next thing you should take a look at is the requirements.
First, the Version tells us what we need to even be able to use this method. Since this function is from the Windows API we need the Windows Installer.We need to reference the library (.lib) file. On the MSDN page we see the filename that we need is MSI.lib. So now let's add a reference to it in our project. You do that in your project by right clicking the project name in the Solution Explorer or through the Project, <ProjectName> Properties menu. Expand configuration properties, then expand Linker and click on the Input line item. To the right you should see 'Additional Dependencies'. You want to include the path and file name in quotes here. With most API calls, it's a good idea to have the SDK that correlates to them installed. In this case, hopefully you have the latest Platform SDK installed. If so, you can find this file under C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib and the file is msi.lib. So the path in quotes in the Additional Dependencies line would be:"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib\msi.lib"Failure to include the library file reference will result in an error when you build your project. In this case you get the following errors in the output window:
Error 1 error LNK2001: unresolved external symbol _MsiEnumProductsW@8 MsiEnumProduct_Sample.obj
Error 2 fatal error LNK1120: 1 unresolved externals C:\Documents and Settings\<profile name>\My Documents\Visual Studio 2005\Projects\<Project Name>\Release\ProjectName.exeThe last requirement, the DLL file, should already be met as MSI.DLL exists in the Windows\System32 folder on most machines. If it's not there, the Windows Installer won't run and you won't be able to install any Windows Installer based programs. Time to head to Windows Update and get the latest Windows Installer, eh?Now let's take a look at some sample code. In this example I created a Win32 console application.#include "stdafx.h"#include <windows.h>#include <msiquery.h>#include <iostream>#include <string>using namespace std;
int main(){ TCHAR ProductCode[40]; UINT nResult; string errResult; for (int nIndex = 0;;nIndex++) { nResult = MsiEnumProducts(nIndex,ProductCode); if (nResult != ERROR_SUCCESS) { if (nResult == 1610) errResult = "ERROR_BAD_CONFIGURATION"; if (nResult == 87) errResult = "ERROR_INVALID_PARAMETER"; if (nResult == 259) errResult = "ERROR_NO_MORE_ITEMS"; if (nResult == 8) errResult ="ERROR_NOT_ENOUGH_MEMORY"; cout << errResult << endl; break; } _tprintf(TEXT(" %4d - %s\r\n"), nIndex, ProductCode); } cin.ignore(); return 0;}I'm using two different ways to display results, cout and _tprintf. The reason I'm doing this is because cout will let me display standard text and strings, but it won't be able to cast TCHAR types to strings for display. All this code is doing is using the msiEnumProducts function to display the product code (TCHAR ProductCode[40]) at index number (int nIndex). We cycle through these from index number 0 on up in increments of 1 until msiEnumProducts returns ERROR_NO_MORE_ITEMS. ErrResult is set to the value of whatever error code msiEnumProducts gives us.
Anyone who has tried to run two Windows Installer-based installations at the same time knows that you can't do this. Only one MSI-based installation can run at a time. So what if you need to run multiple MSI packages one after the other? There is an easy solution to this using vbscript.
Consider the following code:
' =====================================================================
Option Explicit
runit()
sub runit()Dim ret, oInstaller
Set oInstaller = CreateObject("WindowsInstaller.Installer")
ret = oInstaller.InstallProduct("c:\Testsetupfiles\testsetup1.msi", "")
ret = oInstaller.InstallProduct("c:\Testsetupfiles\testsetup2.msi", "")
ret = oInstaller.InstallProduct("c:\Testsetupfiles\testsetup3.msi", "")
end sub' ==========================================================================
What we're doing here is simple. We create variables ret and oInstaller and then set oInstaller to an instance of a Windows Installer object. Then we use the WindowsInstaller.InstallProduct method to call the installations. Since we have three lines of code here making the call, we're running three different installations as you can see by the different file names. The last two lines beginning with "ret =" wait until the line above it has run completely to start the installation. This script allows you to specify a local directory where your MSI file resides or you can specify a UNC path such as \\ServerName\ServerShare\MSIName
You can also expand on the above code to run the installations of all MSI files in a shared folder on the client machine. Consider the following:
' ==========================================================================
sub runit() On Error resume next dim FSO, ret, oInstaller dim fn, f, fc Set FSO = CreateObject("Scripting.FileSystemObject") Set oInstaller = CreateObject("WindowsInstaller.Installer")
set f = fso.getfolder("C:\Testsetupfiles") set fc = f.files
for each fn in fc ret = oInstaller.InstallProduct(f & "\" & fn.name & "", "") Next
end sub
Though it looks more complex, it really isn't. We're setting instances of the File System Object (FSO) and Windows Installer (oInstaller) and declaring variables we'll need to ennumerate the files in the target directory.We set f equal to our target folder C:\TestSetupFiles. Again, this can be substituted for a UNC path to a share on the network. Then we set fc to all of the files in our directory (f). Finally, by using a for each loop we cycle through all of the files (fc), setting the currently cycled file to fn and then call the WindowsInstaller.InstallProduct method to run the installation. In this line of coderet = oInstaller.InstallProduct(f & "\" & fn.name & "", "")we're parsing the directory f, adding a backslash and then ending with the filename (fn.name). This will work for all MSI files in a given directory. I added the On Error Resume Next line in the code because it's possible you will have more than just MSI files in the specified folder and obviously you can't install a .TXT file.
Team Foundation Server for VS.NET 2005 can be a difficult program to install. The best advice I can give is to follow the Install Guide as closely as possible. Still, even the most prepared machine can have problems. Below are some of the errors that can appear and their most common resolutions. The list will be updated as new errors and resolutions are discovered. TFS Installation
I'll post more errors and solutions as they become available.
Below are listed some feedback taken directly from customers supported by Microsoft on ways to improve Visual Studio .NET 2005 and MSDN 2005 (please feel free to email me to add more to this list or comment the blog directly):
You know how when you build a Windows Service and then include a setup and deployment project with your application and then during the installation a Log in screen appears? There may come a time when you want to install a Windows Service silently and not see that log in screen.
First, I need to point out that the Windows Installer and the installer for the Windows service are two separate entities and Windows Installer has no influence on the service except to run the EXE. I've also tried passing properties to a service installer EXE manually but was not successful.