[小ネタ] EC2 Windows Serverの壁紙について

2020.06.30

小ネタです。

以前の登壇資料でも軽く触れていますが、EC2 Windows Serverではログイン時の壁紙にインスタンス自身の情報が表示されており、この機能はEC2ConfigおよびEC2Launchによって行われています。

(壁紙の右上にインスタンス情報が表示される)

本記事ではこの機能の詳細について説明します。

Windowsで壁紙を更新するといえば...

Windowsにおいて壁紙を動的に更新するツールとしてはBgInfoが一番メジャーです。

BgInfoはAzureでも壁紙を更新する拡張機能(BGInfoExtension)として提供されています。

(AzureでBGInfoExtensionをインストールした場合の表示)

ではAWSでもBgInfoを使っているのかというとそうではありません。

Windows Server 2016以降の場合 (EC2Launch v1)

Windows Server 2016以降ではEC2 Windows Serverの初期設定はEC2Launchによって行われます。
EC2LaunchはPowerShellモジュールで実装されており、モジュールの中のSet-Wallpaperという関数で壁紙の更新を行っています。

この関数の実装は実際に見ていただくとわかるのですが、インスタンスメタデータからインスタンス情報を取得しWin32 APIのSystemParametersInfo関数をP/Invokeで実行し壁紙を更新するという極めて愚直な実装となっています。

実装の一部を以下に引用します。

# 最新のモジュール(Ver.1.3.2003040)から処理を一部引用

# Import-WallpaperUtil.ps1
function Import-WallpaperUtil
{
    try 
    {
        $check = [WallpaperUtil]
    }
    catch
    {
        Add-Type -TypeDefinition @"
            using System.Runtime.InteropServices;
            using System;

            namespace WallpaperUtil {

                public static class Helper {
                    private static uint SPI_SETDESKWALLPAPER = 20;
                    private static uint SPI_GETDESKWALLPAPER = 115;
                    private static uint SPIF_UPDATEINIFILE = 0x01;
                    private static uint SPIF_SENDWININICHANGE = 0x02;
                    private static uint MAX_PATH = 1024;

                    public static void SetWallpaper(string src) {
                        PInvoke.SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, src, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
                    }

                    public static String getWallpaper() {
                        var path = new String('\0', (int)MAX_PATH);
                        PInvoke.SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, path, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
                        path = path.Substring(0, path.IndexOf('\0'));
                        return path;
                    }
                }

                public static class PInvoke {
                    [DllImport("user32.dll", CharSet = CharSet.Auto)]
                    public static extern uint SystemParametersInfo(
                        uint action, uint uParam, string vParam, uint winIni);
                }
            }
"@
    }

# Set-Wallpaper.ps1より引用
function Set-Wallpaper
{
    param (
        [Parameter(Position=0)]
        [switch] $Initial
    )

    if (Test-NanoServer)
    {
        return
    }
    
    # Import the wallpaper util methods.
    Import-WallpaperUtil

    # ・・・中略・・・

    try
    {
        Add-Type -AssemblyName System.Windows.Forms

        $fontStyle = "Calibri"
        $fontSize = 12

        Write-Log "Rendering instance information on wallpaper"

        $width = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Width
        $height = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Height

        $textfont = New-object System.Drawing.Font($fontStyle, $fontSize, [System.Drawing.FontStyle]::Regular)
        $textBrush = New-Object Drawing.SolidBrush ([System.Drawing.Color]::White)
    
        $proposedSize = New-Object System.Drawing.Size([int]$width, [int]$height)
        $messageSize = [System.Windows.Forms.TextRenderer]::MeasureText($message, $textfont, $proposedSize)

        if (-not $currentWallpaperPath)
        {
            # Check and create a new wallpaper if no wallpaper is set in current system.
            Write-Log "No wallpaper is set.. Setting wallpaper with custom color"
            $bgrRectangle = New-Object Drawing.Rectangle(0, 0, [int]$width, [int]$height)
            $bgrBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::Navy)
            $bmp = New-object System.Drawing.Bitmap([int]$width, [int]$height)
            $graphics = [System.Drawing.Graphics]::FromImage($bmp)
            $graphics.FillRectangle($bgrBrush, $bgrRectangle)
        }
        else
        {
            # Get the bitmap from the current wallpaper and set the size to be fit in screen.
            Write-Log "Wallpaper found.. Rendering instance information on current wallpaper"
            $srcBmp = [System.Drawing.Bitmap]::FromFile($originalWallpaperPath)
            $bmp = New-Object System.Drawing.Bitmap($srcBmp, $width, $height)
            $graphics = [System.Drawing.Graphics]::FromImage($bmp)
            $srcBmp.Dispose()
        }

        # Set the position and size of the text box with rectangle.
        $rec = New-Object System.Drawing.RectangleF(($width - $messageSize.Width - 20), 30, ($messageSize.Width + 20), $messageSize.Height)
        $graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
        $graphics.DrawString($message, $textfont, $textBrush, $rec)

        # Save the new wallpaper in destination defined above.
        $bmp.Save($customWallpaperPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)

        # Finally, set the wallpaper!
        [WallpaperUtil.Helper]::SetWallpaper($customWallpaperPath)

        Write-Log "Successfully rendered instance information on wallpaper"
    }
    catch 
    {
        Write-Log ("Failed to render instance information on wallpaper {0}" -f $_.Exception.Message)
    }
    finally
    {
        if ($graphics)
        {
            $graphics.Dispose()
        }
        if ($bmp)
        {
            $bmp.Dispose()
        }
    }

    # Before finishing the script, complete the log.
    Complete-Log
}

そしてこのSet-Wallpaperを呼び出すバッチファイルがAdministratorのスタートアップフォルダに配置されており、ログインの都度呼び出される様になっています。

Windows Server 2012 R2以前の場合 (EC2Config)

Windows Server 2012 R2以前ではEC2 Windows Serverの初期設定はEC2Configによって行われます。
EC2ConfigはWindowsサービスで実装されており、壁紙の更新は同梱されているEc2WallpaperInfo.exeによって行われています。

処理の実装としてはEC2Launchの場合と同じ模様です。
(逆にEC2LaunchがEC2Configの実装を移植している雰囲気を感じます...)

また、EC2Configの場合はSettingsフォルダにあるWallpaperSettings.xmlで壁紙に表示する内容をある程度コントロールできます。

このEc2WallpaperInfo.exeを呼び出すショートカットファイルがAllUsersのスタートアップフォルダに配置されており、ログインの都度呼び出される様になっています。

[2020年7月9日追記] EC2Launch v2の場合

先日EC2ConfigおよびEC2Launch v1の後継バージョンであるEC2Launch v2がリリースされました。

EC2Launch v2はGo言語で実装されたWindowsサービスで、壁紙の描画機能はプログラム本体であるec2launch.exeにより行われています。
また、これまでの実装とは異なりベースとなる壁紙の画像ファイルを設定により指定可能となっており、デフォルトではC:\ProgramData\Amazon\EC2Launch\wallpaper\Ec2Wallpaper.jpgを使う様になっています。
ちなみに呼び出しタイミングはEC2Configと同様とでした。

最後に

ざっとこんな感じです。
普段何気なく利用している機能ですが、実装を知っておくとトラブルシューティングの際に役に立つこともあると思います。