しばたテックブログ

気分で書いている技術ブログです。

PowerShellGetをアップデートしよう

PowerShell Advent Calendar 2017、18日目です。

qiita.com

PowerShell 5.0以降、Windows 10やWindows Server 2016では標準でインストール済みのPowerShellGetの機能について、現時点での最新バージョンはVer.1.6.0なのですが、例えばWindows 10においてはFall Creator Update(1709)を適用してもVer.1.0.0.1と古いバージョンのまま更新されません。

www.powershellgallery.com

PowerShellGetの変更履歴はこちらあり、これまでいくつかのバグフィックスや機能追加がなされています。
例えば最新版のVer.1.6.0ではプレリリースバージョンのモジュールのインストールに対応する様になっています。

一般論としてできるだけ新しいバージョンのモジュールを利用していくのが良いため、本エントリではその手順を紹介していきます。

PowerShellGetをアップデートする

本エントリではWindows 10 Fall Creators Update(1709)を検証環境として説明します。

はじめに更新前のバージョンをチェックしてみます。
PowerShellGetは内部でPackageManagementモジュールを使用するのでこちらのバージョンも確認しておきます。

Get-Module PowerShellGet,PackageManagement -ListAvailable

f:id:stknohg:20171218001908p:plain

どちらのモジュールもVer.1.0.0.1と古いままです。

通常、モジュールのアップデートにはUpdate-Moduleコマンドレットを使いますが、何も考えずに実行すると以下のエラーメッセージが出てしまいます。

Update-Module PowerShellGet 

f:id:stknohg:20171218002004p:plain

このためInstall-Moduleコマンドレットを使い新しいバージョンを別にインストールする体をとる必要があります。
Install-Moduleコマンドレットですが、インストールするモジュールを過去バージョンと併用するには-Forceオプションが必要です。
また既存コマンドの動作を上書き許可するため-AllowClobberオプションも必要になります。

Install-Module PowerShellGet -Scope CurrentUser -Force -AllowClobber 

インストール後はこんな感じになります。

f:id:stknohg:20171218002024p:plain

  • PowerShellGet : Ver.1.6.0
  • PackageManagement : Ver.1.1.7

と、両モジュールとも新しいバージョンが追加インストールされていることが分かります。

コンソールを再起動すると新しいバージョンのモジュールが読み込まれ利用可能になります。

PowerShellGetの新機能を試してみる

最初に新しいバージョンのPowerShellGetでは

プレリリースバージョンのモジュールのインストールに対応する

と説明しました。

ちょうど今PowerShellコンソールに様々な拡張機能を提供しているPSReadlineモジュールがVer.2.0 ベータをプレリリース版として公開しているので試しにインストールしてみます。

PSReadline 2.0は1.x系に対して主にPowerShell Coreへの対応とバグフィックスが主な変更点ですが、

  • 日本語入力時にキャレット位置が狂うバグの修正(#542)
  • 行頭でバックスペースキーを押したときにBeep音がならない様に修正 (#422)

などの日本語ユーザーにとっても嬉しい修正が入っているので積極的に試してみると良いと思います。

PSReadline 2.0 Betaをインストールしてみる

プレリリース版のモジュールを取り扱うには新たに追加された-AllowPrereleaseオプションを指定してやります。

モジュールを検索する場合にはFind-Module-AllowPrereleaseオプションを指定します。

Find-Module PSReadline -AllowPrerelease 

f:id:stknohg:20171218002156p:plain

-AllowPrereleaseを付けたときだけプレリリース版であるVer.2.0.0-beta1がヒットするのが見て取れます。

続けて、プレリリース版のモジュールをインストールするにはInstall-Module-AllowPrereleaseオプションを指定してやります。
過去バージョンのモジュールと併用するには-Forceオプションが必要です。
また、既存のバージョン(ここではVer.1.2)が署名済みのため、Ver.2.0でも署名を求められており、いまのところは-SkipPublisherCheckを付けてやる必要があります。

Install-Module PSReadline -AllowPrerelease -Scope CurrentUser -Force -SkipPublisherCheck

f:id:stknohg:20171218002355p:plain

エラー無く処理が終わればインストール完了です。
コンソールを再起動すると新しいバージョンのモジュールが読み込まれます。

補足 システム全体へのインストール

ここまでの手順では各コマンドを-Scope CurrentUserで実行してユーザー毎のインストールを行いましたが、もちろん管理者としてシステムワイドにインストールしても構いません。

この場合は-AllowClobberオプションは不要になります。

Install-Module PowerShellGet -Force

f:id:stknohg:20171218002432p:plain

コンソールを再起動すると新しいバージョンのモジュールが読み込まれます。

同様にPSReadlineも管理者としてインストール可能です。

Install-Module PSReadline -AllowPrerelease -Force -SkipPublisherCheck

f:id:stknohg:20171218002452p:plain

続・Get-Randomで使われている乱数生成アルゴリズムについて調べてみた

PowerShell Advent Calendar 2017、13日目です。

qiita.com

以前に書いた、

blog.shibata.tech

の続きになります。

本エントリではPowerShell 6.0でGet-Randomで使われている乱数生成アルゴリズムがどう変化したか調べた結果について説明します。

はじめに

これまでのバージョンのPowerShellとPowerShell 6.0で大きく異なる点は、

  • PowerShell Coreとして.NET Core製になったこと
  • クロスプラットフォーム化

になります。

PowerShellはオープンソースになったので実際のソースを確認しながら上記の点を中心に変化を調べていきます。

PowerShell 6.0におけるGet-Randomの実装

現時点で最新のPowerShell 6.0-RC(.NET Core 2.0.0)におけるGet-Randomの実装は、これまで通りGetRandomCommandクラスにあります。

ソースの基本的な構成はPowerShell 5.0~5.1の構成と大きく変わっておらず、乱数生成にはPolymorphicRandomNumberGeneratorクラスが使われています。

このクラスでは、-SetSeedパラメーターを指定してシードを指定した場合はSystem.Randomクラスを、シードを指定しない場合はSystem.Security.Cryptography.RandomNumberGeneratorクラスのRandomNumberGenerator.Create()メソッドから生成されるインスタンスを使って乱数生成を行います。

ここまでは前回と同様なのですが、それぞれのクラスは.NET Core版なので.NET Framework版のそれとは実装が異なります。

System.Randomクラスの実装について

System.Randomクラスの実装は.NET Coreでも変わっておらず、乱数生成にはKnuthの減算法に基づいたアルゴリズムが使用されます。
こちらについては変化はなく全てのプラットフォームにおいて共通です。

The Art of Computer Programming (2) 日本語版 Seminumerical algorithms Ascii Addison Wesley programming series

The Art of Computer Programming (2) 日本語版 Seminumerical algorithms Ascii Addison Wesley programming series

  • 作者: Donald E.Knuth,有沢誠,和田英一,斎藤博昭,長尾高弘,松井祥悟,松井孝雄,山内斉
  • 出版社/メーカー: アスキー
  • 発売日: 2004/10
  • メディア: 単行本
  • 購入: 1人 クリック: 80回
  • この商品を含むブログ (44件) を見る

System.Security.Cryptography.RandomNumberGeneratorクラスの実装について

System.Security.Cryptography.RandomNumberGenerator.Create()メソッドの実装は.NET Coreでは大きく変わっています。

前回の場合ではこのメソッドによりSystem.Security.Cryptography.RNGCryptoServiceProvideクラスのインスタンスが生成されていましたが、.NET CoreではSystem.Security.Cryptography.RandomNumberGeneratorImplementationクラスのインスタンスが生成され、このRandomNumberGeneratorImplementationの実装はプラットフォーム毎に異なります。

WindowsにおけるRandomNumberGeneratorImplementationクラスの実装

RandomNumberGeneratorImplementationクラスのWindowsに固有の実装はこちらにあり、以下の様になっています。

using System.Diagnostics;

namespace System.Security.Cryptography
{
    partial class RandomNumberGeneratorImplementation
    {
        private unsafe void GetBytes(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count > 0);

            Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(pbBuffer, count);
            if (status != Interop.BCrypt.NTSTATUS.STATUS_SUCCESS)
                throw Interop.BCrypt.CreateCryptographicException(status);
        }
    }
}

乱数生成には

Interop.BCrypt.BCryptGenRandom(pbBuffer, count);

System.Interop.BCryptクラスが使われ以下の様に定義されています。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

internal partial class Interop
{
    internal partial class BCrypt
    {
        internal static unsafe NTSTATUS BCryptGenRandom(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count >= 0);

            return BCryptGenRandom(IntPtr.Zero, pbBuffer, count, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
        }

        private const int BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002;

        [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
        private static extern unsafe NTSTATUS BCryptGenRandom(IntPtr hAlgorithm, byte* pbBuffer, int cbBuffer, int dwFlags);
    }
}

最終的に乱数生成にはCryptography API: Next Generation(CNG)BCryptGenRandom関数が使われます。

この関数で使われるアルゴリズムについてですが...よくわかりませんでした。
セキュリティに詳しい方がいらっしゃいましたら是非教えてください。

【ちょっと追記】

BCryptGenRandom関数の定義に、

The default random number provider implements an algorithm for generating random numbers that complies with the NIST SP800-90 standard, specifically the CTR_DRBG portion of that standard.

とあるので、デフォルトではNIST SP 800-90というアルゴリズムが使われるようです。

.NET Coreの実装はおそらくデフォルト指定なのでPowerShellにおいもこのアルゴリズムが使われていそうです。
ただ、このアルゴリズムがどんなものなのかはさっぱりです...

Unix(Linux)におけるRandomNumberGeneratorImplementationクラスの実装

RandomNumberGeneratorImplementationクラスのUnixに固有の実装はこちらにあり、以下の様になっています。

using System.Diagnostics;

namespace System.Security.Cryptography
{
    partial class RandomNumberGeneratorImplementation
    {
        private unsafe void GetBytes(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count > 0);

            if (!Interop.Crypto.GetRandomBytes(pbBuffer, count))
            {
                throw Interop.Crypto.CreateOpenSslCryptographicException();
            }
        }
    }
}

乱数生成には

Interop.Crypto.GetRandomBytes(pbBuffer, count);

とUnix固有のSystem.Interop.Cryptクラスが使われています。
このクラスの実装に関して、使用されるアルゴリズム毎に実装されるファイルが分かれているのですが、GetRandomBytesに関しては"おそらく"Interop.RAND.csファイルの実装が使われる様です。*1

この実装については以下の様になっています。

using System.Diagnostics;
using System.Runtime.InteropServices;

internal static partial class Interop
{
    internal static partial class Crypto
    {
        internal static unsafe bool GetRandomBytes(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count >= 0);

            return CryptoNative_GetRandomBytes(pbBuffer, count);
        }

        [DllImport(Libraries.CryptoNative)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern unsafe bool CryptoNative_GetRandomBytes(byte* buf, int num);
    }
}

内部で呼ばれているCryptoNative_GetRandomBytesの実装はこちらにあり、抜粋すると以下の実装となっています。

/*
Function:
GetRandomBytes
Puts num cryptographically strong pseudo-random bytes into buf.
Return values:
Returns a bool to managed code.
1 for success
0 for failure
*/
extern "C" int32_t CryptoNative_GetRandomBytes(uint8_t* buf, int32_t num)
{
    int ret = RAND_bytes(buf, num);

    return ret == 1;
}

最終的にはOpenSSLのRAND_bytes関数が使われています。

アルゴリズムについては...こちらもよくわかりませんでした...orz

【ちょっと追記】

細かい話がWikiに載っているようなので、気になる方は調べてみるとよいかもしれません。

macOSにおけるRandomNumberGeneratorImplementationクラスの実装

最後に、RandomNumberGeneratorImplementationクラスのmacOSに固有の実装はこちらにあり、以下の様になっています。

using System.Diagnostics;

namespace System.Security.Cryptography
{
    partial class RandomNumberGeneratorImplementation
    {
        private unsafe void GetBytes(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count > 0);

            Interop.AppleCrypto.GetRandomBytes(pbBuffer, count);
        }
    }
}

乱数生成には

Interop.AppleCrypto.GetRandomBytes(pbBuffer, count);

System.Interop.AppleCryptoクラスが使われ以下の様に定義されています。

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

internal static partial class Interop
{
    internal static partial class AppleCrypto
    {
        internal static unsafe void GetRandomBytes(byte* pbBuffer, int count)
        {
            Debug.Assert(pbBuffer != null);
            Debug.Assert(count >= 0);

            int errorCode;
            int ret = AppleCryptoNative_GetRandomBytes(pbBuffer, count, out errorCode);

            if (ret == 0)
            {
                throw CreateExceptionForCCError(errorCode, CCRNGStatus);
            }

            if (ret != 1)
            {
                throw new CryptographicException();
            }
        }

        [DllImport(Libraries.AppleCryptoNative)]
        private static extern unsafe int AppleCryptoNative_GetRandomBytes(byte* buf, int num, out int errorCode);
    }
}

内部で呼ばれているAppleCryptoNative_GetRandomBytesの実装はこちらにあり、以下の実装となっています。

#include "pal_random.h"

#include <CommonCrypto/CommonCrypto.h>
#include <CommonCrypto/CommonRandom.h>

extern "C" int32_t AppleCryptoNative_GetRandomBytes(uint8_t* pBuf, uint32_t cbBuf, int32_t* pkCCStatus)
{
    if (pBuf == nullptr || pkCCStatus == nullptr)
        return -1;

    CCRNGStatus status = CCRandomGenerateBytes(pBuf, cbBuf);
    *pkCCStatus = status;
    return status == kCCSuccess;
}

最終的に乱数生成にはCommonCryptoCCRandomGenerateBytes関数が使われています。

アルゴリズムは、というかこのライブラリ自体がどういったものかよくわかりません。
OS標準の機能なんですよね?多分...
Macに詳しい方なら余裕でわかるものなのでしょうか?

まとめ

アルゴリズムの詳細についてはわからないことだらけでしたが、実装については明らかにできたかと思います。

前回の分と合わせてGet-Randomの実装をまとめると、

  • PowerShell 2.0 ~ 4.0

    • シード指定あり => .NET FrameworkのSystem.Randomクラスで乱数生成
    • シード指定なし => .NET FrameworkのSystem.Randomクラスで乱数生成
  • PowerShell 5.0 ~ 5.1

    • シード指定あり => .NET FrameworkのSystem.Randomクラスで乱数生成
    • シード指定なし => .NET FrameworkのSystem.Security.Cryptography.RNGCryptoServiceProviderクラスで乱数生成
  • PowerShell 6.0

    • シード指定あり => .NET CoreのSystem.Randomクラスで乱数生成
      • Randomクラスの実装は全プラットフォーム共通
    • シード指定なし => .NET CoreのSystem.Security.Cryptography.RandomNumberGeneratorImplementationクラスで乱数生成
      • RandomNumberGeneratorImplementationクラスの実装はプラットフォーム毎に異なる

となります。

*1:正直ちょっと自信がありません...

PowerShell Core 6.0をデバッグ実行する - Visual Studio Code編

PowerShell Advent Calendar 2017、11日目です。

qiita.com

PowerShell 6.0のGAリリースも間近となりビルド・デバッグ手順も固まってきたと思えたので本エントリを書きます。

はじめに

PowerShellがオープンソース化した時点でPowerShellのビルドはVisual Studio 2015で行われており、ソリューションの構成で、

  • .NET Frameworkで動作するWindows PowerShell
  • NET Coreで動作するPowerShell Core

をそれぞれ個別にビルドできる様になっていました。

その後PowerShell 6.0がPowerShell Coreのみをリリースする方針となってからは、Windows PowerShell専用の処理を削除しつつ、Visual Studio 2017とVisual Studio Codeでビルド・デバッグができる様に変更されています。
(ただ、Visual Studio 2017でのビルドについては現在も目下修正中の様で手元の環境ではうまくいきませんでした。追記しました)

細かい話はGitHub上の、

PowerShell/docs/building at master · PowerShell/PowerShell · GitHub

PowerShell/docs/debugging at master · PowerShell/PowerShell · GitHub

のドキュメントに記載されていますので興味があればご覧ください。

PowerShellをデバッグ実行する

本エントリではWindows環境、64bit Windows 10(1709)でVisual Studio Codeを使用した場合の手順を紹介します。
他のOSについては先述のGihHubのドキュメントに手順がありますのでそちらをご覧ください。

1. Gitのインストール

ソースコードをCloneするのにGitクライアントが必要です。
適当にインストールしてgitコマンドが使える様にしておいてください。

git-for-windows.github.io

2. Visual Studio Codeのインストール

Visual Studio Codeを適当にインストールしてください。

code.visualstudio.com

今回は64bit版のVer.1.18.1をインストールします。
また、以下の拡張が必要になるのでインストールしておいてください。

  • C#拡張 (現時点のバージョンは1.13.1)
  • PowerShell拡張 (現時点のバージョンは1.5.1)

f:id:stknohg:20171206162748p:plain

f:id:stknohg:20171206162759p:plain

3. ソースコードのClone

PowerShellのリポジトリよりソースコードをCloneします。

ソースコードを修正したい、Pull Requestを送りたいといった場合であればForkしてからのCloneとなるでしょうが、今回はとりあえずデバッグ実行できれば良いのでリポジトリを直接Cloneします。

任意のディレクトリで以下の様に--recursiveオプションを付けてgit cloneしてください。

git clone --recursive https://github.com/PowerShell/PowerShell.git

実行後PowerShellディレクトリが作成されソースコードがCloneされているはずです。

4. その他必要ツールのインストール

PowerShellをビルド・デバッグ実行するには上記の他に

が必要となります。

これらのツールを個別にインストールしても構わないのですが、PowerShellのソースコードにはビルトのための専用モジュールがあり、このモジュールの機能を利用することで必要なツールをまとめてインストールすることができます。

ソースコードのルートディレクトリにbuild.psm1というモジュールがあるので、これをインポートしStart-PSBootstrap関数を実行すると必要なツールをまとめてインストールしてくれます。

# cd [PowerShellのルートディレクトリ]
cd .\PowerShell\
Import-Module .\build.psm1
Start-PSBootstrap

f:id:stknohg:20171209195521p:plain

このStart-PSBootstrapでは$env:LOCALAPPDATA\Microsoft\配下のフォルダに必要なツールをインストールします。
現時点では、

  • .NET Core SDK : Ver.2.0.2
  • PowerShell 6.0 : Ver.6.0.0-rc
  • RCEdit : Ver.0.2.0

がインストールされます。
(今後インストールされるバージョンは上がっていくでしょう)

ここで、仕様なのかバグなのか不明なのですが、.NET Core SDKをインストールした後にdonetコマンドのパス($env:LOCALAPPDATA\Microsoft\dotnet)がPATH環境変数に永続して登録されませんでした。
このため、手動でPATHに登録するかビルド・デバッグ実行の都度PATHに追加しておく必要があります。

5. ビルドとデバッグ実行

最後にVisual Studio Codeを起動し、PowerShellをデバッグ実行してみます。

# dotnetコマンドのPATHを通してからVisual Studio Codeを実行
$env:PATH += ";$env:LOCALAPPDATA\Microsoft\dotnet"
code

PowerShellにはC++のソースも一部含まれているため、初回起動時に以下の様なダイアログが出ますが放置して大丈夫です。

f:id:stknohg:20171206162852p:plain

ここでデバッグの構成をみると、下図の様に、

  • .NET Core Launch
  • .NET Core Attach

が登録されており、.NET Core Launchを実行すればPowerShellのビルトとデバッグ実行が可能です。
F5キー実行でも構いません。

f:id:stknohg:20171206162911p:plain

最初のビルドにはしばらく時間がかかりますので焦らず待ちます。
ビルドエラーが出る様であれば適宜対処してください。

f:id:stknohg:20171206162944p:plain

最終的には下図の様にPowerShellをデバッグ実行することができます。

f:id:stknohg:20171206163006p:plain

【補足】PowerShellをビルドする

PowerShellをビルドするだけであればVisual Studio Codeが無くても先述のbuild.psm1モジュールを使い、Start-PSBuildコマンドで可能です。

# Start-PSBuildのパラメーターはこの例の他にもいろいろあります
Import-Module .\build.psm1
Start-PSBuild -Configuration Release

最後に

とりあえずこんな感じです。
PowerShellをデバッグ実行することでソースの修正はもちろんですが、怪しいと思った挙動の詳細を細かく追うことも可能になります。

みなさんも是非トライしてみてください。


【2018/04/12追記】

Visual Studio 2017を使った手順を書きました。

blog.shibata.tech