VMWare WorkstationでWindows10のクローンを展開するときにいちいちコルタナさんの声を聴きたくない

はじめに

こんにちは!コルタナと申します。

特に需要は無かったのですが、Sysprepとか応答ファイルについて軽く触れてみたかったのでVMWare上でクローンを展開するときに自動でOOBEをスキップ出来るように設定する方法を確認してみました。

まぁ、よくあるSysprep芸なので社内インフラ担当者からしたら当たり前な内容です。

ゴール

最終的には以下の要件を満たせるようにしたいと思います。

  • クローンして起動した時点でVMWare Toolsとベースイメージ作成時の最新パッチがインストール済み
  • VMWare上でクローン操作したらあとは放置するだけでAdministratorでデスクトップ画面まで出す

手順

以下の手順で操作していきます。

  1. Windows ADK用のインスタンスのインストール・セットアップ
  2. 応答ファイルの作成
  3. クローン用ベースイメージのセットアップ
  4. Sysprep

ここではWindows10 Enterprise Evalutionを使用します。

Windows ADK用のインスタンスのインストール・セットアップ

とりあえず、VMWare上に一つ仮想マシンを立ててそこにWindowsをインストールしADKをインストールします。

f:id:jyuch:20180607215321p:plain

ここで1回目の

こんにちは!コルタナと申します。

を聞くことになります。

普通にWindowsのインストールを完了させ、Windows ADK(Assessment and Deployment Kit)をインストールします。

f:id:jyuch:20180607220350p:plain

ここではDeployment Kit以外は特にいらないのでチェックを外しておきます。

f:id:jyuch:20180607220555p:plain

応答ファイルの作成

ベースイメージをインストールするのに使用するインストールメディアからinstall.winをローカルにコピーします。

f:id:jyuch:20180607221600p:plain

インストールイメージからカタログファイルを作成します。この辺はググれば出てくるのでさっと流します。

応答ファイルをいい感じに作ります。

f:id:jyuch:20180607222402p:plain

f:id:jyuch:20180607222411p:plain

f:id:jyuch:20180607222418p:plain

f:id:jyuch:20180607222434p:plain

f:id:jyuch:20180607222441p:plain

f:id:jyuch:20180607222449p:plain

f:id:jyuch:20180607222512p:plain

f:id:jyuch:20180607223425p:plain

最後に検証して完了です。

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <CopyProfile>true</CopyProfile>
            <TimeZone>Tokyo Standard Time</TimeZone>
            <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
            <OEMInformation>
                <Logo>C:\Windows\System32\oemlogo.bmp</Logo>
                <Manufacturer>jyuch</Manufacturer>
                <SupportURL>http://jyuch.hatenablog.com/</SupportURL>
            </OEMInformation>
        </component>
    </settings>
    <settings pass="generalize">
        <component name="Microsoft-Windows-PnpSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <DoNotCleanUpNonPresentDevices>true</DoNotCleanUpNonPresentDevices>
            <PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
        </component>
        <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SkipRearm>1</SkipRearm>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <AutoLogon>
                <Username>Administrator</Username>
                <Enabled>true</Enabled>
            </AutoLogon>
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <ProtectYourPC>3</ProtectYourPC>
            </OOBE>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>ja-JP</InputLocale>
            <SystemLocale>ja-JP</SystemLocale>
            <UILanguage>ja-JP</UILanguage>
            <UserLocale>ja-JP</UserLocale>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim:c:/sysprep/install.wim#Windows 10 Enterprise Evaluation" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

OEMのロゴを設定とかしていますが、これは別に必要な作業ではありません。

クローン用ベースイメージのセットアップ

ふつうにVMの作成・Windowsをセットアップし、セキュリティパッチとVMWare Toolsをインストールします。

f:id:jyuch:20180607223944p:plain

検証環境作成のためにVMを作りまくっているおかげでVM作成RTAでそこそこの記録を出せる気がします。

ここで2回目の

こんにちは!コルタナと申します。

を聞きますが、最後なので我慢しましょう。

あとは好きなソフトウェアやらドライバをインストールします。

終わりましたら監査モードに入ります。

f:id:jyuch:20180607231028p:plain

監査モードに入ったらVMWare Toolsをインストールするのに使用したアカウントを消し去りつつ、クローン後のデスクトップ設定を作りこみます。 ここでの設定がクローン後に作成されるアカウントのデフォルトのプロファイルになります。

ちなみに、ここでの設定は以下の記事の『3. クローン用パソコンで設定を作り込む』を参考にしました。

harukeee.hatenablog.com

f:id:jyuch:20180607232853p:plain

一通り設定が完了したらSysprepする前にOEMのロゴイメージをしれっと紛れ込ませておきます。

Sysprep

最後に先ほど作った応答ファイルで2回目のSysprepを行います。

確認

Sysprepが完了したらVMWare上でVMのクローンを行い、目的の状態まで自動で設定されるか確認します。

おわりに

やってみて感じたのですが、何かと設定を忘れてSysprep後に思い出して憤りを感じながらプログレスバーが伸びるを眺めることになるので、設定するリストをあらかじめ作成してから作業に挑んだほうがいいと思います。

あと、試行錯誤のために何度*1Windowsをインストールしたので、しばらくはWindowsのインストール画面は見たくないです。

おわり

小ネタ

f:id:jyuch:20180608225254p:plain

メーカはこうやって自社のロゴを挟んでいるんですね。

参考

www.tenforums.com

*1:Windows Serverの検証も行ったので、3週間で合わせて50回ほど

Akka.NETでもStreamsでUDPを使いたい

はじめに

Nignxから吐き出されるSyslogを受け取ってデータベースにぶち込みたいけどその為だけにFluentdをインストールもしたくないしそもそもWindowsでSyslogを扱うのは色々つらみポイントが高いんだよねってことで、Akka.NET Streamsで流し込みたいけど標準で用意されているSourceにUDPないじゃんってことで、とりあえずUDPをSourceで扱う方法を確認してみました。

ちなみに元ネタは↓です。

github.com

Reactive Streamsの規格に準拠するのは割と難しいのでActor​Publisher<T>を使うのはやめんしゃいとのことなので、まぁ、この記事は参考にしないほうがいいんじゃないかな(ポロローン

stackoverflow.com

UDP

using Akka;
using Akka.Actor;
using Akka.Event;
using Akka.IO;
using Akka.Streams.Actors;
using System;
using System.Collections.Generic;
using System.Net;
using Akka.Streams.Dsl;

namespace AkkaStreamsUdp
{
    public class UdpSource : Actor<200b>Publisher<Udp.Received>
    {
        public static Props Props(EndPoint listenOn, int maxBufferSize) =>
            Akka.Actor.Props.Create(() => new UdpSource(listenOn, maxBufferSize));

        public static Source<Udp.Received, IActorRef> Create(EndPoint listenOn, int maxBufferSize) =>
            Source.ActorPublisher<Udp.Received>(Props(listenOn, maxBufferSize));

        private Queue<Udp.Received> datagrams = new Queue<Udp.Received>();
        private readonly int maxBufferSize;
        private readonly ILoggingAdapter log = Logging.GetLogger(Context);

        public UdpSource(EndPoint listenOn, int maxBufferSize)
        {
            if (maxBufferSize <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(maxBufferSize));
            }

            this.maxBufferSize = maxBufferSize;
            Context.System.Udp().Tell(new Udp.Bind(Self, listenOn));
        }

        protected override bool Receive(object message)
        {
            return message
                .Match()
                .With<Udp.Bound>(() => Context.Become(Ready(Sender)))
                .With<Cancel>(() => Context.Stop(Self))
                .WasHandled;
        }

        private Receive Ready(IActorRef socket)
        {
            return (object message) =>
            {
                return message
                .Match()
                .With<Udp.Received>(r =>
                {
                    if (datagrams.Count >= maxBufferSize)
                    {
                        log.Warning($"Datagram buffer size {maxBufferSize} exceeded");
                        Context.Become(Suspend(socket));
                    }
                    else if (datagrams.Count == 0 && TotalDemand > 0)
                    {
                        OnNext(r);
                    }
                    else
                    {
                        datagrams.Enqueue(r);
                        Deliver();
                    }
                })
                .With<Request>(() => Deliver())
                .With<Udp.Unbind>(() => socket.Tell(Udp.Unbind.Instance))
                .With<Udp.Unbound>(() => OnCompleteThenStop())
                .With<Cancel>(() => socket.Tell(Udp.Unbind.Instance))
                .WasHandled;
            };
        }

        private Receive Suspend(IActorRef socket)
        {
            return (object message) =>
            {
                return message
                .Match()
                .With<Udp.Received>(() => log.Debug("Dropping UDP datagram while suspended"))
                .With<Request>(() =>
                {
                    Deliver();
                    log.Info("Datagram buffer size is ok, resuming");
                    Context.Become(Ready(socket));
                })
                .WasHandled;
            };
        }

        private void Deliver()
        {
            while (TotalDemand > 0 && datagrams.Count > 0)
            {
                var elem = datagrams.Dequeue();
                OnNext(elem);
            }
        }
    }
}

んで、こんな感じで使えばokです。

using Akka.Actor;
using Akka.Streams;
using System;
using System.Net;
using System.Text;

namespace AkkaStreamsUdp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var system = ActorSystem.Create("udp-test"))
            using (var materializer = system.Materializer())
            {
                var source = UdpSource.Create(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6000), 100);

                source.RunForeach(r =>
                {
                    Console.WriteLine(r.Data.ToString(Encoding.UTF8));
                }, materializer);

                Console.ReadLine();
            }
        }
    }
}

ペイロードUTF-8としてデコードしてひたすらコンソールに吐き出し続けます。

おわりに

とりあえずUDPを受け取って下流に流すことが出来たので、あとは途中のFlowとSinkをいい感じにごにょごにょすればデータベースに突っ込めそうです。

おわり

github.com

C#でもノンブロッキングでTCPしたい

はじめに

好きな魚雷はMk48 どうも弊社です。

特に需要があったわけではないのですが、C#からTCPをノンブロッキングで扱う方法を調べてみました。

普通に『C# TCP ノンブロッキング』でググるこのドキュメントが出てきますが、コールバック祭りはつらみポイントがかなり高いので我らがAkka.NET Streamsで処理する方法を確認してみました。

というよりエコーするだけでこの分量とかヤバすぎでしょ。

Streams

今回のコードは改行で区切られた数字を受け取り、加算したのち加算した値を返すコードを書いてみます。

また、時間がかかる処理のエミュレートとしてTask.Delayを挟みます。

using Akka.Actor;
using Akka.IO;
using Akka.Streams;
using Akka.Streams.Dsl;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tcp = Akka.Streams.Dsl.Tcp;

namespace AkkaStreamTcp
{
    class Program
    {
        private static int i = 0;

        static void Main(string[] args)
        {
            var system = ActorSystem.Create("akka-stream-tcp");
            var materializer = system.Materializer();

            Source<Tcp.IncomingConnection, Task<Tcp.ServerBinding>> connections =
                system.TcpStream().Bind("localhost", 8888);

            connections.RunForeach(connection =>
            {
                Console.WriteLine($"New connection from: {connection.RemoteAddress}");

                var echo = Flow.Create<ByteString>()
                    .Via(Framing.Delimiter(
                        ByteString.FromString("\n"),
                        maximumFrameLength: 256,
                        allowTruncation: true))
                    .Select(c => c.ToString())
                    .SelectAsync(1, Sum)
                    .Select(ByteString.FromString);

                connection.HandleWith(echo, materializer);
            }, materializer);

            Console.ReadLine();
        }

        static async Task<string> Sum(string value)
        {
            if (int.TryParse(value, out int n))
            {
                Interlocked.Add(ref i, n);
            }

            await Task.Delay(1000);
            return $"{i,-11}";
        }
    }
}

クライアント側は多数のクライアントのエミュレートとしてBroadcastPoolを使用してActorを100倍界王拳として同時接続を行います。

using Akka.Actor;
using Akka.Routing;
using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;

namespace AkkaStreamTcpClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var system = ActorSystem.Create("client");
            var props = Props.Create<ClientActor>().WithRouter(new BroadcastPool(100)).WithDispatcher("default-fork-join-dispatcher");
            var actor = system.ActorOf(props, "client");

            for (var i = 0; i < 100; i++)
            {
                actor.Tell(10);
            }

            Console.ReadLine();
        }
    }

    public class ClientActor : ReceiveActor
    {
        private Guid id;

        public ClientActor()
        {
            id = Guid.NewGuid();

            ReceiveAsync<int>(async i =>
            {
                var sw = Stopwatch.StartNew();

                using (var client = new TcpClient("localhost", 8888))
                using (var stream = client.GetStream())
                {
                    var message = Encoding.ASCII.GetBytes($"{i,-11}\n");
                    await stream.WriteAsync(message, 0, message.Length);
                    await stream.FlushAsync();
                    var response = new byte[11];
                    await stream.ReadAsync(response, 0, 11);
                    var n = Encoding.ASCII.GetString(response);
                    Console.WriteLine($"{id} {n} {sw.ElapsedMilliseconds}");
                }
            });
        }
    }
}
48b9c0da-b281-40eb-8ed5-1984ef11ab53 100000      1036
26f112cc-15d6-44d5-9502-fae81dcfda25 100000      1038
aa56fb77-6421-49e0-9c3a-b42019100fde 100000      1037
2db39b91-d205-4980-ac57-0e93f0c9c6ec 100000      1022
ed7aea6e-9915-4fed-9b13-a65f679d2da4 100000      1012

処理分の遅延だけで各接続が処理出来ているようです。

ちなみにTask.DelayThread.Sleepに変えると

bade5a2a-6f14-4d31-ba42-02d7dca6fa47 100000      1793
a777e9d3-6bac-4f2c-b947-474abd2a397f 100000      1924
20ae8e25-3a71-4567-b893-0deb50fb5a3d 100000      1951
0d29f288-22e9-4567-bc03-fa16bd220dfd 100000      1802
05ca3475-f7c2-403a-a42f-0378c9c9ebd4 100000      1782

と各接続の処理時間が伸びています。まぁそうだよねスレッドをブロックしたらまずいよね

おわりに

Akka.NETつよい