Thursday, June 19, 2014

Painkiller Black ending script

Alistor is extremely difficult to understand so here is a transcription.

Daniel: Hold on Eve, I’ll get you out of here. (Demons surround Daniel. Alastor draws near.)

Alastor: Well done Daniel... well done.

Daniel: Alastor? What... I... I thought I killed you?

Alastor: Wha... Kill me? [laugh] You can’t kill me up there... you simply sent me back here. We can, however, be destroyed down here. [Motions toward Lucifer] I think he should have finished you off when he had the chance... but in the end, he was blinded by his infatuation with Eve, and it got the best of him... [laugh] I knew it would.

You put up more of a fight than I thought you would… Absorbing those demon souls has given you a tremendous amount of strength... I think it might have clouded your judgment.... Welcome to hell!

Daniel: I... I don’t understand...

Alastor: Of course you don’t, you foolish human. We could have overthrown Heaven ages ago, but Lucifer wasn’t confident we were ready. He thought we needed more demons... He thought we lacked the strength necessary. He gave them too much credit and wasted too much time. He was weak, he was shortsighted... and he had to be removed from power. [...sentence inaudible...] We all came here for Lucifer... You just happened to beat us to it. I didn’t think you could destroy him... regardless, had you not done it, we surely would have. I must admit I was shocked to see that you declined the offer into heaven. Ooh... bad decision.

Daniel: When I’m finished here, I’ll make my way up there.

Alastor: When you’re finished here? Ha! You’ll never be finished here! Ooh, I’m going to torture you for eternity... I’m going to enjoy watching you scream with pain as I slowly gut you. Don’t worry... you won’t feel much.

Daniel: Bring it on.

Thursday, June 12, 2014

Writing a Useful Windows Service in .NET in Five Minutes

This helpful blog post about writing a Windows Service didn't allow comments so I'm writing a comment here.

I don't like to have to call external utilities to install and uninstall things; I think programs should be able to install and uninstall themselves, so I tried to figure out how to write Install() and Uninstall() methods using .NET code. Here's what I came up with:
[System.ComponentModel.RunInstaller(true)]
public class MyServiceInstaller : Installer
{
    private ServiceProcessInstaller processInstaller;
    private ServiceInstaller serviceInstaller;

    public MyServiceInstaller()
    {
        processInstaller = new ServiceProcessInstaller(); // "to write registry values associated with services you want to install."
        serviceInstaller = new ServiceInstaller();        // "to write registry values associated with the service to a subkey within the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services registry key"

        processInstaller.Account = ServiceAccount.LocalSystem;
        serviceInstaller.StartType = ServiceStartMode.Automatic;
        serviceInstaller.ServiceName = ServiceBaseClassName.Name;

        Installers.Add(serviceInstaller);
        Installers.Add(processInstaller);
    }

    public static void Install() // throws on failure
    {
        // MyServiceInstaller has an Install() method but we can't call it 
        // directly, because a NullReferenceException occurs in its bowels.
        //var installer = new MyServiceInstaller();
        
        // So instead we will ask AssemblyInstaller to scan this assembly,
        // and call Install() on our behalf.
        var installer = new AssemblyInstaller(Assembly.GetExecutingAssembly(), new string[] { "/ShowCallstack" });
        // Doc says: «Setting this property to true creates a new file named 
        // "{Assembly name}.InstallLog" to log messages for this assembly. Setting 
        // UseNewContext to false prevents the new file from being created.»
        installer.UseNewContext = true;

        var pointlessComplication = new Hashtable(); // who knows why
        // The documentation states "If all the Install methods succeed, the 
        // Commit method is called. Otherwise, the Rollback method is called."
        // That is WRONG, Install() does not call Commit().
        installer.Install(pointlessComplication);
        // However it probably doesn't matter if we call Commit() or not 
        // because ServiceInstaller and ServiceProcessInstaller do not override 
        // the Commit() method.
        installer.Commit(pointlessComplication);
        // Note: the service is not started yet (no, I don't know how.)
    }
    public static void Uninstall()
    {
        // Likewise, don't call Uninstall(), it throws NullReferenceException.
        // The exception that occurs on failure is wrong. For instance if the
        // service is not installed, the debugger tells you that an exception
        // occurs with the following message:
        //   "The specified service does not exist as an installed service."
        //
        // However, this exception is discarded (not preserved as an 
        // InnerException as it should be). Instead you get two nested 
        // InstallExceptions and both of them contain this brain-dead message:
        //   "An exception occurred while uninstalling. This exception will be 
        //   ignored and the uninstall will continue. However, the application 
        //   might not be fully uninstalled after the uninstall is complete."
        // 
        // The real exception is appended in the *.InstallLog file, although
        // there is no clear separation in that file between the install 
        // information and the uninstall information (e.g. no newlines).

        //var installer = new MyServiceInstaller();
        var installer = new AssemblyInstaller(Assembly.GetExecutingAssembly(), new string[] { "/ShowCallstack" });
        installer.UseNewContext = true;
        var pointlessComplication = new Hashtable();
        installer.Uninstall(pointlessComplication);
        installer.Commit(pointlessComplication);
    }
}
As you can see, I had an (undocumented) problem with NullReferenceException, but I worked it out.

Install() seems to work perfectly (the service appears on the service list, and no exception occurs), but Uninstall() doesn't work properly; the service is uninstalled, yet an exception occurs that says "Service was not found on computer '.'.". This exception is discarded by the uninstaller, then the uninstaller deletes the ServiceName.InstallState file, then it throws the following idiotic exception: "Could not find file 'C:\...\bin\x86\Debug\ServiceName.InstallState'."

Further investigation suggests that something silently went wrong during the install process, even though ServiceName.InstallLog does not show any problems. After a "successful" installation, running "C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil" /u C:\...\bin\x86\Debug\ServiceName.exe produces the following warning:
The file containing the saved state for the C:\...\bin\x86\Debug\ServiceName.exe assembly, located at C:\...\bin\x86\Debug\ServiceName.InstallState, could not be read, and the file might have been corrupted. The uninstall will continue without the saved information.
Huh. Okay. So next, based on a decompilation of System.Configuration.Install.ManagedInstallerClass.InstallHelper(), I basically uninstalled the same way it does:
public static void Uninstall()
{
    var installer = new AssemblyInstaller(Assembly.GetExecutingAssembly(), new string[] { "/ShowCallstack" });
    installer.UseNewContext = true;

    TransactedInstaller tInstaller = new TransactedInstaller();
    tInstaller.Installers.Add(installer);

    tInstaller.Uninstall(null);
}
Internally, the same exception occurs, "Service was not found on computer '.'." But the exception is caught and discarded and, as before, the service actually is uninstalled. Thanks TransactedInstaller! Unfortunately, using TransactedInstaller to perform the installation doesn't make any difference (if you use TransactedInstaller to perform the installation, InstallUtil /u still claims that the InstallState "might have been corrupted".)

Finally I tried this simple version:
public static void Install() // throws on failure
{
    ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location, "/ShowCallStack", });
}
public static void Uninstall()
{
    ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location, "/ShowCallStack", "/u" });
}
If you decompile InstallUtil you'll find that it's an "empty shell"; all it does it call this InstallHelper() method! However, still this code does not behave quite the same way as the real InstallUtil.exe; in fact it behaves exactly as before: the install and uninstall both succeed, but the uninstall involves a swallowed "Service was not found on computer '.'." exception and if you use InstallUtil.exe /u to uninstall, there is a message saying that the InstallState "could not be read". And again, if uninstall fails, the exception that contains the actual reason is thrown away and replaced by a generic message.