PowerShellでWindowsサービスは作成できなかった

PowerShellスクリプトの中にVBなりC#のコードを埋め込んで、埋め込んだコードを実行中にコンパイルしてインスタンス生成する方法があるのをこの間知って(参考)、それってもしかしてPowerShellスクリプトだけでWindowsサービスプログラムとか作れるんじゃね?と思って試してみたが残念、出来なかった。


.NetでWindowsサービスプログラムを作る一般的な話については、MSDNのServiceBaseのリファレンスが参考になる(参考


とりあえず作ってみたPowerShellスクリプトは以下(動作しないが)。ほとんどC#だけど。


$provider = new-object Microsoft.CSharp.CSharpCodeProvider
$params = new-object System.CodeDom.Compiler.CompilerParameters
$params.GenerateInMemory = $True
$refs = "System.dll","System.ServiceProcess.dll","mscorlib.dll"
$params.ReferencedAssemblies.AddRange($refs)

$txtCode = @'
using System;
using System.ServiceProcess;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS
{
public int serviceType;
public int currentState;
public int controlsAccepted;
public int win32ExitCode;
public int serviceSpecificExitCode;
public int checkPoint;
public int waitHint;
}

public enum State
{
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007,
}

class SampleService : ServiceBase
{
[DllImport("ADVAPI32.DLL", EntryPoint = "SetServiceStatus")]
public static extern bool SetServiceStatus(
IntPtr hServiceStatus, SERVICE_STATUS lpServiceStatus);

private SERVICE_STATUS myServiceStatus;

public SampleService()
{
this.ServiceName = "Sample Service";
this.CanStop = true;
}

protected override void OnStart(string[] args)
{
IntPtr handle = this.ServiceHandle;

myServiceStatus.currentState = (int)State.SERVICE_RUNNING;
SetServiceStatus(handle, myServiceStatus);
}

protected override void OnStop()
{
IntPtr handle = this.ServiceHandle;

myServiceStatus.currentState = (int)State.SERVICE_STOPPED;
SetServiceStatus(handle, myServiceStatus);
}
}
'@

$results = $provider.CompileAssemblyFromSource($params, $txtCode)
echo $results.Errors
$assembly = $results.CompiledAssembly

if ($assembly) {
$service = $assembly.CreateInstance("SampleService")
[System.ServiceProcess.ServiceBase]::Run($service)
}


Windowsサービスとして登録するコマンドは以下


>sc create SampleService5 binPath= "C:\Windows\System32\cmd.exe -c C:\WIN
DOWS\system32\windowspowershell\v1.0\powershell.exe C:\Work\SampleService.ps1"


んで登録してみても、残念起動するときに「サービスが制御機能に応答しません」とメッセージが表示されサービスは起動しない。
イベントログを見てみるとアプリケーションログにソース:.NET Runtime、イベントID:1023、メッセージが「アクティブなコンソール出力バッファのハンドルを取得中に Win32 内部エラー "アクセスが拒否されました。" 0x5 が発生しました。」の記録があり、こいつを解決するのは難しそうなのでやめた。


いわゆるSI構築の現場でサーバ屋が運用ツールを作る際に、保守とか開発環境維持とかの都合でコンパイルが必要かそうでないかがその機能を作れるかどうかを分けるメインファクターになったりすることがしばしばあり、上記の方法が使えればOSの標準機能だけ(Longhornサーバからは)かつ一応スクリプトのみでWindowsサービスプログラムを作れるようになるので、出来れば非常に面白かったのだけれども。