Power Autoamte Desktopを導入

遅ればせながらPower Automate Desktop(以下PAD)を導入。

以前はPowershell + UIAutomation.dllでデスクトップアプリケーションの自動化を行っていた。同じ処理をPADで再実装してみたが、実装にかかる作業時間が1/5程度になっている感じでありがたい。Powershell + UIAutomation.dllで記述したときには画面が表示されるのを待つためのループ処理やディレイ処理が多く遅かったのだが、PADで記述したことでディレイ処理は1箇所のみに減り、大幅に早くなっている。

ただしいくつか気になる点もある。

1.複数のMicrosoftアカウントをWindowsに関連付けていると動作しない。実行時例外が発生して「The cache contains multiple tokens satisfying the requirements. Try to clear token cache.」となってしまう。あまり使わない方のMicrosoftアカウントの連携を解除して解決した。

2.圧縮→ZIPファイルが日本語ファイル名に対応しておらず、文字化けしてしまう。OSSのUnZip(http://infozip.sourceforge.net/UnZip.html)を子プロセスとして呼ぶことで対応した。

3.コードの見にくさ。WEB上のMicrosoft Flowも何度か使ってみた事はあるのだが、一画面に20ステップ程度しか表示出来ないので、やはり複雑な処理は書きにくい。

4.バグを見つけたのでレポートしたいが、お手軽なバグレポートツールが見当たらない。ヘルプ辺りにレポートメニューがあると有り難いのだが。

総じてデスクトップアプリの自動化を行うツールとして、無償でここまで扱いやすいツールは他に無く、とてもよいと思う。だがWEBアプリ操作やファイル処理を行おうとすると、なかなか厳しい。その辺りは適材適所、SeleniumやVBAを併用する事は続きそうだ。

Active Directory環境でDHCPを使用する。

Active Directory環境でDHCPを使用する場合には、DHCPサーバとADサーバとの間で信頼関係を結び、ADサーバ上のDNSに登録されているクライアントPCのIPアドレスを更新する必要があります。

ADサーバーとの間で認証する都合上、必然的に使用できるDHCPサーバーは限られます。LinuxとSMBを使っても出来るはずですが、実際に構築したという情報も少なく面倒そうです。

DHCP自体は負荷の高いサービスでは無いので、ADサーバーとDHCPサーバーを兼ねるのが良さそうです。各ネットワークセグメントにDHCP Relayサーバーを配置して、中央のADサーバにDHCPの問合せを送ります。

参考:More about authorizing DHCP servers in AD DS

Server 2003/2008からP2Vで仮想化したときにブルースクリーン(0x0000007B)が発生する。

Windows Server 2003やWindows Server 2008の環境でP2Vツールを使用して物理サーバーから仮想環境(Hyper-V等)に移行した場合に、OSの起動時にブルスクリーンが発生して0x0000007Bのエラーが発生する場合がある。これは元の物理サーバーの環境でRAIDやSCSIを使用していたために、標準のSATA関係のドライバが組み込まれていないことに原因があります。

C:\Windows\System32\に以下のドライバが無い場合には、正常な環境から複製してください。

C:\windows\system32\drivers\intelide.sys
C:\windows\system32\drivers\pciide.sys
C:\windows\system32\drivers\atapi.sys

ドライバを読み込ませるために、以下のレジストリエントリが無い場合には作成してください。

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7111
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7110&cc_0601
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\primary_ide_channel
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\secondary_ide_channel
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\IntelIde
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\PCIIde
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\atapi

設定すべき値は以下を参照してください。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7111]
"Service"="intelide"
"ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}"

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7110&cc_0601]
"ClassGUID"="{4D36E97D-E325-11CE-BFC1-08002BE10318}"
"Service"="isapnp"

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\primary_ide_channel]
"Service"="atapi"
"ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}"

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\secondary_ide_channel]
"Service"="atapi"
"ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}"

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\IntelIde]
"ErrorControl"=dword:00000001
"Group"="System Bus Extender"
"Start"=dword:00000000
"Tag"=dword:00000004
"Type"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\
  52,00,49,00,56,00,45,00,52,00,53,00,5c,00,69,00,6e,00,74,00,65,00,6c,00,69,\
  00,64,00,65,00,2e,00,73,00,79,00,73,00,00,00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\IntelIde]
"ErrorControl"=dword:00000001
"Group"="System Bus Extender"
"Start"=dword:00000000
"Tag"=dword:00000004
"Type"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\
  52,00,49,00,56,00,45,00,52,00,53,00,5c,00,69,00,6e,00,74,00,65,00,6c,00,69,\
  00,64,00,65,00,2e,00,73,00,79,00,73,00,00,00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\atapi]
"ErrorControl"=dword:00000001
"Group"="SCSI miniport"
"Start"=dword:00000000
"Tag"=dword:00000019
"Type"=dword:00000001
"DisplayName"="標準 IDE/ESDI ハード ディスク コントローラ"
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\
  52,00,49,00,56,00,45,00,52,00,53,00,5c,00,61,00,74,00,61,00,70,00,69,00,2e,\
  00,73,00,79,00,73,00,00,00

P2Vで仮想化する前の元の環境で設定を変更できれば良いのですが、それが出来ない場合には仮想ディスクファイル(*.VHD)をマウントして編集します。レジストリを編集するにはレジストリエディタを起動し”ファイル→ハイプの読み込み”から、マウントした仮想ディスクの”\windows\system32\config\system”を開きます。

参考:P2V Migration Issues with Hyper-V: STOP: 0x0000007B

「対象のパスが長すぎます」のエラーが出てファイルを操作できないとき。

「対象のパスが長すぎます」のエラーが出てエクスプーラー上からファイルを削除する事が出来なくなったとき、コマンドプロンプトからファイル名の先頭に\\?\を付けてDELコマンドで削除すると良い。

DEL \\.\C:\Users\hogehoge\Desktop\abc長いファイル名xyz.txt

Windowsは本来なら32768文字までのファイル名を扱うことが出来る。しかし古いアプリケーションとの互換性を維持するために、普段使用できるファイル名は260文字までに制限している。先頭の\\.\は互換性のための制限を解除する指示に当たる。

SharpZipLibの日本語対応

SharpZipLib 1.2.0以前でtarファイルを作成すると日本語が文字化けしていたけど、何時の間にか日本語に対応していたんですね。TarOutputStreamのConstructorでEncodingを指定できるではないですか。

github(https://github.com/icsharpcode/SharpZipLib)から最新のソースコードをダウンロードしてきて、自らビルドしましょう。

WevDAV上のExcelファイルを更新すると”アップロードできませんでした この場所の保存内容をアップロードするには、サインインする必要があります。”のエラーになる

QNAPのNASの機能を使ってWevDAVの共有フォルダを作成しました。共有フォルダ上のファイルををOffice 2016で開くとファイル更新時に「アップロードできませんでした この場所の保存内容をアップロードするには、サインインする必要があります。」と言うエラーが発生します。調べたところなかなか根の深い問題で、どうにもMicrosoft Officeアップローダーの仕様上の問題に起因するようです。

ちなみにWevDAV共有フォルダ上のファイルをエクスプローラーで操作したり、Office以外のアプリケーションから読み書きする場合には問題がありません。

Microsoft OfficeアップロードセンターはOffice 2013以降に追加された機能です。通信が不安定な環境において、更新されたWebDAV上のファイルをキャッシュして、非同期的にWebDAVサーバーに送信する機能を提供しています。MicrosoftはOneDrive上のOfficeファイルを開くときに、Microsoftが独自に認証周りを拡張したWebDAVを使用しています。OneDriveのファイルをOfficeで直接扱うためのモジュールとして広く使われています。この独自に拡張しているというところが曲者で、Windows統合認証やMicrosoft Liveアカウント認証に対応していないサーバーの場合に、認証に失敗してしまうようなのです。

Windows 10の場合はタスクトレイに常駐しているMicrosoft OneDrive設定から「ファイルのコラボレーション」を無効にすると、Microsoft Officeアップローダーを止められるようです。Googleから”UPLOAD FAILED: You are required to sign in to upload your changes to this location”で検索すると色々と情報が見つかりますが、OfficeやOSのバージョンによって設定すべき項目が異なったり、公式な対策でもないようなのでなかなか面倒そうです。

この辺りまで調べたところで、WebDAVの使用はあきらめる事にしました。

参考:UPLOAD FAILED: You are required to sign in to upload your changes to this location

letsencryptの証明書更新で”‘Namespace’ object has no attribute ‘standalone_supported_challenges'”が発生する

OSをUbuntu 18.04LTSからUbuntu 20.04LTSに移行したところ、旧システムから移行してきた証明書の更新に、以下のようなエラーを表示して失敗しました。

> sudo letsencrypt renew
...
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.example.net.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewal configuration file /etc/letsencrypt/renewal/www.example.net.conf (cert: www.example.net) produced an unexpected error: 'Namespace' object has no attribute 'standalone_supported_challenges'. Skipping.
...

letsencryptのバージョンが上がったことによりstandalone_supported_challengesオプションがサポートされなくなったことが原因です。

/etc/letsencrypt/renewal/www.example.net.confを編集して、最後の行のstandalone_supported_challengesをコメントアウトすると正常に動作するようになりました。

# renew_before_expiry = 30 days
cert = /etc/letsencrypt/live/www.example.net/cert.pem
privkey = /etc/letsencrypt/live/www.example.net/privkey.pem
chain = /etc/letsencrypt/live/www.example.net/chain.pem
fullchain = /etc/letsencrypt/live/www.example.net/fullchain.pem
version = 0.31.0
archive_dir = /etc/letsencrypt/archive/www.example.net

# Options and defaults used in the renewal process
[renewalparams]
authenticator = standalone
account = 52bc4f19233c146191f7f576945ed0d9
server = https://acme-v01.api.letsencrypt.org/directory
# standalone_supported_challenges = "tls-sni-01,http-01"

参考:https://github.com/certbot/certbot/issues/6984

C:\Windows\Tempにイベントログファイルが作成される。

C:\Windows\Tempにイベントログファイルが作成され、C:ドライブの空き容量が無くなる。

設定→システム→記憶域で見ると一時ファイルが多くの容量を使用しているが、一時ファイルの内容を確認してみると明細の合計が、一時ファイルで使用している容量よりもはるかに小さい。また一時ファイルの削除を行ってもC:ドライブの空き容量が回復しない。

実際に何処のフォルダにファイルが作成されて居るのか確認したところ、C:\Windows\Tempフォルダに大量のファイルが作成されて居ることを確認できた。どうやらC:\Windows\Temp以下のフォルダは自動では削除されないらしい。

本来ならイベントログファイルはC:\Windows\System32\winevt\Logsに作成される。検索してみたところ”Low Disk Space error due to a full Temp folder“が見つかった。どうもWindows UpdateやMicrosoft Storeの障害にともなう不具合のようである。上記の手順でトラブルシュート後にWindows Updateを適用する事で解決した。

C:\Windows\Tempにイベントログファイルが作成される。

C:\Windows\Tempにイベントログファイルが作成され、C:ドライブの空き容量が無くなる。

設定→システム→記憶域で見ると一時ファイルが多くの容量を使用しているが、一時ファイルの内容を確認してみると明細の合計が、一時ファイルで使用している容量よりもはるかに小さい。また一時ファイルの削除を行ってもC:ドライブの空き容量が回復しない。

実際に何処のフォルダにファイルが作成されて居るのか確認したところ、C:\Windows\Tempフォルダに大量のファイルが作成されて居ることを確認できた。どうやらC:\Windows\Temp以下のフォルダは自動では削除されないらしい。

本来ならイベントログファイルはC:\Windows\System32\winevt\Logsに作成される。検索してみたところ”Low Disk Space error due to a full Temp folder“が見つかった。どうもWindows UpdateやMicrosoft Storeの障害にともなう不具合のようである。上記の手順でトラブルシュート後にWindows Updateを適用する事で解決した。

.NET COREでLinuxで動作するサービスを作成する

.NET Core + C#でLinuxのDeamon作れないかなと思ったら、思ったより簡単に作れるようになっていたので覚え書き。Program.csとDaemonService.csの中味はほぼ定型なのでサンプルからそのまま流用します。

以下のサンプルは.NET Core 3.1で記述しています。

// Program.cs

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace DaemonSample
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config.AddEnvironmentVariables();

                    if (args != null)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddOptions();
                    services.Configure<DaemonConfig>(hostContext.Configuration.GetSection("Daemon"));

                    services.AddSingleton<IHostedService, DaemonService>();
                })
                .ConfigureLogging((hostingContext, logging) => {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                });

            await builder.RunConsoleAsync();
        }
    }
}
// DaemonService.cs

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DaemonSample
{
    class DaemonService : IHostedService, IDisposable
    {
        private readonly ILogger _logger;
        private readonly IOptions<DaemonConfig> _config;
        public DaemonService(ILogger<DaemonService> logger, IOptions<DaemonConfig> config)
        {
            _logger = logger;
            _config = config;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting daemon: " + _config.Value.DaemonName);
            _logger.LogInformation("arg1:{0}", _config.Value.arg1);
            _logger.LogInformation("arg2:{0}", _config.Value.arg2);

            // ここでサーバー本体の処理をTaskとして起動します。

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Stopping daemon.");
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _logger.LogInformation("Disposing....");
        }
    }
}
// DaemonConfig.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace DaemonSample
{
    class DaemonConfig
    {
        public string DaemonName { get; set; }
        public string arg1{ get; set; }
        public string arg2{ get; set; }
    }
}

起動時のコマンドラインは下記の通り。コマンドライン引数の受け渡し方が非常に独特です。

/usr/bin/dotnet run --project deamonsample --configuration Release --Daemon:DaemonName="Sample Daemon" Daemon:arg1="argument 1" Daemon:arg2="argument 2"

Demonとして起動するにはsystemdを使用します。/etc/systemd/systemにdeamonsample.serviceファイルを作成し、下記の様に記述します。

[Unit]
Description=deamon sample
After=network.target

[Service]
ExecStart=/usr/bin/dotnet run --project atozipsrv --configuration Release --Daemon:DaemonName="deamonsample " Daemon:arg1="argument 1" Daemon:arg2="argument 2"
WorkingDirectory=/usr/src/deamonsample
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=atozip
Environment=DOTNET_CLI_HOME=/var/src/deamonsample

[Install]
WantedBy=multi-user.target

後は通常のsystemdのDeamon同様に以下のコマンドで起動します。

$ systemctl start deamonsample

参考:Creating a Daemon with .NET Core (Part 1)