Powershellを使って正しく認証プロキシ経由でWebアクセスする

概要

  • プロキシの自動構成スクリプト(.pac)に従って、正しい認証プロキシの認証を通した上で、Powershellでwebアクセスしたい。
  • つまり、アクセスするURLに対応するプロキシのURLを取得して、Invoke-Webrequestに渡してWebアクセスしたい。
  • [System.Net.WebRequest]::GetSystemWebProxy()を使うとOS設定を利用して、URLに対応するプロキシを取得することができる。

実際

OSでプロキシ設定がなされていることを前提としている

# 各種設定
$URL = "http://example.com"

# 認証情報の生成(本来は暗号化して外だししておく)
$ProxyUser = "User"
$ProxyPassword = "PassWord"
$securePassword = ConvertTo-SecureString $ProxyPassword -AsPlainText -Force
$proxyCredntial  = System.Management.Automation.PSCredential ($ProxyUser, $securePassword)

#システムのProxy設定を利用して、URLに対応するProxy経由でアクセス
$SystemProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$proxy = $SystemProxy.GetProxy($URL)
Invoke-Webrequest $URL -Proxy $proxy.AsoluteUri -ProxyCredential $proxyCredntial  Method Get

環境はWindows10で実行しているので、Powerhell 5.0以降であれば動作するはず。

その他

  1. web上にはデフォルトプロキシを設定してしまうという例が散見されるが、汎用性に欠ける。

  2. プロキシの自動構成スクリプト(.pac)、要はJavascriptなので、ScriptControllerで実行してやろうと思った。しかし、ScriptControllerは64bit OSでは廃止されており不可能だった。

  3. 実用を考えるなら、パスワードはスクリプトに記載しせず、事前にSecureStringを利用してファイルに保存しておき、スクリプトの中でCredentialを生成する必要すべき。*1

  4. 例示用のドメインについてはRFC2606を参照のこと。

*1:正直なところ、私はSecureStringについて正しく理解しているとは言い難い。どこかで調べてまとめたい

携帯性だけを追求したBluetoothキーボード「Wekey Pocket PN301」

スマホ向けのキーボードを探していて、非常に薄くて軽いBluetoothキーボード「Wekey Pocket PN301」を購入したのでそのメモ。

良いところ

軽い。重さは95gとなんと100g以下である。

薄い。薄さは約5mmほど。

小さい。サイズはだいたいシステム手帳のバイブルサイズのリフィルぐらい。

上記のように携帯性が非常に良いので、気軽にカバンに入れておける。

また、二つ折りの本体を開くと電源が入り、すぐ使えるところも地味にポイントが高い。

悪いところ

キーボードとしての機能はあまり良くない。 打鍵感が全くないので、タイプの抜けがとても多い。また、ほぼただの板なので、タイピングは非常に固く長時間タイプするとだいぶ疲れそう。

その他

ペアリングはFn+P、キーサウンドはFn+BackSpaceで調整することができる。 キーボード配列が英語なので、気になる人は気になるかもしれない。

まとめ

メインで利用するというよりは、ちょっと外出・旅行するときに、スマホと一緒にお守り代わりに持っておくと重宝するかな、という感じだとおもう。

あと、似たような製品で以下のようなものがあり、重さが2倍になるが、打鍵感はこちらの方がよさそう。

WindowsのVS Codeでtextlintを使ってリアルタイムに文章校正する。

Windows上のVS Codeでtextlintを使うためにやるべきことは以下の通り。

Node.jsとnpmをインストールする

textlintはNode.js製のプログラムなのでまずはNode.jsとNode.js用のパッケージ管理ツールであるnpmをインストールする。

例えば次のようなページが参考になる⇒Node.js / npmをインストールする(for Windows) - Qiita

上記のページに従って、公式サイトにアクセスし、Windows用のバイナリをダウンロードしてインストールする。 インストールに成功していれば以下のコマンドでバージョンを確認できる。

PS D:\> node --version
v8.10.0
PS D:\> npm --version
5.6.0

textlintをインストールする

npmを使ってtextlintをインストールする。ここでは手っ取り早くグローバルインストールする*1

また、ルールを一から設定するのは手間なので、azuさん作の日本語の技術文書向けプリセットであるtextlint-rule-preset-ja-technical-writingをインストールする。 ついでに例外を明示するためにtextlint/textlint-filter-rule-commentstextlint/textlint-filter-rule-whitelistもインストールする。

PS D:\> npm install -global textlint textlint-rule-preset-ja-technical-writing textlint-filter-rule-comments textlint-filter-rule-whitelist

textlintを設定する

インストール後には設定ファイルを作成する必要がある。 textlintの対象にしたいフォルダへ移動してtextlint --initすると設定ファイルである.textlintrcが生成される。

PS D:\> cd D:\hoge
PS D:\hoge> textlint --init
PS D:\hoge> ls


    ディレクトリ: D:\hoge


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2018/03/23     22:53             34 .textlintrc
-a----       2018/03/23     22:06           1174 textlintのインストール.md

あとは生成された設定ファイルで、先ほどインストールしたプリセットルールを有効にする。

{
  "filters": {
      "comments": true
  },
    "rules": {
        "preset-ja-technical-writing": true
    }
}

トラブルシューティング

ここまでやって動くと思いきや、エラーでるのでそのトラブルシューティング

textlintが動作しない

[Error - 23:04:48] Server initialization failed.
  Message: Request initialize failed with message: Failed to resolve module: textlint
  Code: -32603

textlintへのパスが通っていないから発生したエラー。VS Codeを再起動して解決。

モジュールが利用できない

PS D:\Users\username\hoge> textlint --config .\.textlintrc .\textlintのインストール.md
× Error
Failed to load textlint's filter rule module: "comments" is not found.
See FAQ: https://github.com/textlint/textlint/blob/master/docs/faq/failed-to-load-textlints-module.md


× Stack trace
ReferenceError: Failed to load textlint's filter rule module: "comments" is not found.
See FAQ: https://github.com/textlint/textlint/blob/master/docs/faq/failed-to-load-textlints-module.md

    at TextLintModuleResolver.resolveFilterRulePackageName (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\engine\textlint-module-resolver.js:116:19)
    at TextLintModuleLoader.loadFilterRule (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\engine\textlint-module-loader.js:221:43)
    at C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\engine\textlint-module-loader.js:80:23
    at Array.forEach (<anonymous>)
    at TextLintModuleLoader.loadFromConfig (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\engine\textlint-module-loader.js:79:32)
    at TextLintEngine.AbstractTextLintEngine (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\engine\textlint-engine-core.js:97:27)
    at new TextLintEngine (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\textlint-engine.js:22:47)
    at Object.executeWithOptions (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\cli.js:133:26)
    at Object.execute (C:\Users\username\AppData\Roaming\npm\node_modules\textlint\lib\textlint\src\cli.js:90:25)
    at C:\Users\username\AppData\Roaming\npm\node_modules\textlint\bin\textlint.js:34:20
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:695:11)
    at startup (bootstrap_node.js:188:16)

commentsモジュールが見つからずに発生したエラー。再度全モジュールをnpm install -globalして解消。

コマンドラインでは動くけど、ファイルを開いても拡張機能が動作しない

textlintのVS Code拡張は以下の条件で動作するので、ファイル単位ではなく、フォルダ(プロジェクト)単位で開く必要がある。フォルダを開いて解決。

既に、textlintの設定をしているプロジェクト(node_modulesにtextlintやルールがインストールされていて、.textlintrcが配置されている場所)なら、VS Codeでそのディレクトリを開くだけで動作します。

VS Codeでtextlintを使って文章をチェックする - Qiita

指定のルールだけ設定を変更したい場合(2018/06/24追記)

‘preset-ja-technical-writing‘で一部のルールを無効化したり、設定を変更したりしたいときは、以下のように入れ子に記載する。

{
    "filters": {
        "comments": true
    },
    "rules": {
        "preset-ja-technical-writing": {
            "sentence-length": {
                "max": 100
            }
        }
    }
}

おわりに

ここまでできれば、リアルタイムにlintしながら文章を書くことができる。 ただ、この時点ではデフォルト設定がのみなので、効果は限定的だ。より効果を出すためには、 .textlintrc をカスタマイズして辞書やフィルタを育てていけばよい。 楽をするために頑張って、退屈なことは機械にやらせよう。

*1:Linuxなどにおいてインストールするときは、パーミッションエラー等になることもあるそうなので、よく考えてローカルモードでやった方がよいだろう

Powershellで特定のフォルダの拡張子の一覧を取得する

特定のフォルダ配下に含まれるファイルの拡張子の一覧を取得するための方法のメモ

Powershellを使ってファイルの拡張子を得るにはいくつか方法があって、例えばSystem.IO.Pathクラスを使う手もあったりするが、それよりはSystem.IO.FileInfoクラスを生成してしまった方が楽だし応用が効くように思う。

PS D:\> Get-ChildItem -Recurse -File | %{$_.Extension} | Sort-Object -CaseSensitive | Get-Unique
.log
.txt

やっていることは以下のような流れ

  • Get-ChildItem -Recurse -Fileでカレントディレクトリのファイルをサブディレクトリ含めて取得
  • FileInfoのExtentionフィールドで拡張子を取得して
  • Sort-Object -CaseSensitiveで大文字小文字を区別してソートしたうえで
  • Get-Uniqueでユニークにする(重複を除く)

なお、Get-ChileItemのFileパラメータはv3からしか使えないので、v2の場合はWhere-Objectの抽出条件でFileInfoのAttributeか、PSIsContainerを使って絞りこむことで代替する。

PS D:\SandBox> Get-ChildItem -Recurse | Where-Object{$_.Attribute -ne "Directory" } | %{$_.Extension} | Sort-Object -CaseSensitive | Get-Unique

PS D:\SandBox> Get-ChildItem -Recurse | Where-Object{$_.PSIsContainer -eq $false } | %{$_.Extension} | Sort-Object -CaseSensitive | Get-Unique

また、特定のファイルの拡張子を取得するときはGet-ItemでFileInfoのオブジェクトを取得すればよい。Alias(gi)を使うと楽に書ける。

PS D:\> $(Get-Item .\random.log).Extension
.log

Powershellスクリプト・モジュールのヘルプトピックの作り方

Powershellスクリプト・モジュールで正しくGet-Helpで読むことができるヘルプトピック(Help topic)を作る方法のメモ。

そもそもヘルプトピックとは?

要するにGet-Helpしたときに表示されるヘルプのこと*1。細かいことは詳しいヘルプ情報の取得 | Microsoft Docsを参照。

例えばGet-HelpコマンドレットをGet-Helpすると以下のようになる

PS D:\SandBox> get-help Get-ChildItem

名前
    Get-ChildItem

概要
    Gets the items and child items in one or more specified locations.

(以下略)

上記のようなヘルプトピックは、簡単にPowershellで自作したスクリプト・モジュールでも表示させることができるので、以降はその方法・記法を記載する*2

スクリプト(.ps1)

スクリプトの場合は、コメントベースヘルプ(Comment_Based_Help)をスクリプト内に記述することで、ヘルプトピックを作成できる。 記載場所は関数の宣言のすぐ上かすぐ下で、以下のようなイメージ。

#関数の前に書く場合
  <#
  .SYNOPSIS
   関数の概要
  .DESCRIPTION
   関数の詳細な説明
  .EXAMPLE
   関数の利用例
  .EXAMPLE
   利用例は複数記載できる
  .PARAMETER foo
  パラメータの説明
  .PARAMETER bar
  パラメータの分だけ記載する
  #>
function Do-Hoge {
   (略)
}

関数の中に書く場合
function Do-Fuga {
  <#
  .SYNOPSIS
   関数の概要
  .DESCRIPTION
   関数の詳細な説明
  .EXAMPLE
   関数の利用例
  .EXAMPLE
   利用例は複数記載できる
  .PARAMETER foo
  パラメータの説明
  .PARAMETER bar
  パラメータの分だけ記載する
  #>
 (略)
}

もっと詳しいことはGet-Help about_Comment_Based_Helpを実行するか、以下のWebページを読むとわかると思う。

モジュール(.psm1)

モジュールの場合は、コメントベースヘルプも使えるが、それ以外にもヘルプを外部ファイルに記述して読み込ませることができる。 ファイルには、モジュール全体に対する説明である「概念説明のヘルプ(Conceptual ("About") Help)」と、個々の関数について記載する「コマンドレット ヘルプファイル(Cmdlet Help File)」の2種類がある。

なお、モジュールに関しては以下のブログ記事がとても参考になる。モジュールの自動読み込みについてとか。

モジュールヘルプの場所

以下のような形でヘルプファイルを配置すると、OSの言語によって自動的に読み込むヘルプファイルを切り替えてくれる。

<ModulePath>
         \SampleModule
               \<en-US>
                     \about_SampleModule.help.txt
                     \SampleModule.extention-help.xml
                     \SampleNestedModule.extention-help.xml
               \<ja>
                     \about_SampleModule.help.txt
                     \SampleModule.extention-help.xml
                     \SampleNestedModule.extention-help.xml

このように多言語対応できるところが、この方式の強みだと思う。逆に言うと、Powershellでモジュールを書いて、かつドメスティックに使うだけならComment Basedで十分な気がする。

ロケール(en-USとか)の一覧はロケール ID (LCID) の一覧VBScriptのレファレンスだが、参考になる。

概念説明のヘルプ(Conceptual ("About") Help)

モジュール全体の説明のことで、Writing Help for Windows PowerShell Modules.aspx)に詳しい。 以下のようなファイル(内容は上記サイトからの引用)を「about_hogehoge.help.txt」という名前で作ると、Get-Help about_hogehogeで参照できる。

TOPIC
    about_<subject or module name>

SHORT DESCRIPTION
    A short, one-line description of the topic contents.

LONG DESCRIPTION
    A detailed, full description of the subject or purpose of the module.

EXAMPLES
    Examples of how to use the module or how the subject feature works in practice.

KEYWORDS
    Terms or titles on which you might expect your users to search for the information in this topic.

SEE ALSO
    Text-only references for further reading. Hyperlinks cannot work in the Windows PowerShell console. 

コマンドレット ヘルプファイル(Cmdlet Help File)

モジュールの中のメンバー(関数など)のヘルプをxmlファイル(SampleModule.extention-help.xml)に記載する。 例としては以下のような感じ(MSのサイトからの引用)。

<command:command
  xmlns:maml="http://schemas.microsoft.com/maml/2004/10"
  xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10" 
  xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10">
  <command:details>
    <!--Add name an synopsis here-->
  </command:details>
  <maml:description>
    <!--Add detailed description here-->
  </maml:description>
  <command:syntax>
    <!--Add syntax information here-->
  </command:syntax>
  <command:parameters>
    <!--Add parameter information here-->
  </command:parameters>
  <command:inputTypes>
    <!--Add input type information here-->
  </command:inputTypes>
  <command:returnValues>
    <!--Add return value information here-->
  </command:returnValues>
  <maml:alertSet>
    <!--Add Note information here-->
  </maml:alertSet>
  <command:examples>
    <!--Add cmdlet examples here-->
  </command:examples>
  <maml:relatedLinks>
    <!--Add links to related content here-->
  </maml:relatedLinks>
</command:command>

とりあえず、参考になりそうなのは以下のサイト。よめば雰囲気でだいたいわかる。特に最後のSAPIEN社の公式ブログの「What do I name the XML help file?」はファイル命名規則について詳しく参考になる。

しかし、多言語対応するにはこのファイルを作ることが必要なのだが、残念なことにコマンドが自動でやってくれていた補完が効かない。 例えばParameterのattributeなど、コードで明示していることも、いちいち自分で書かないといけない。生のxmlを、しかも英語のレファレンスを見ながら書かざるを得ず、とてもつらい。

Git-Hubを見るとコードからヘルプ生成を自動化するプロジェクトも散見されるので、そのうち使って記事にしたいと思う。たとえばPowerShell/platyPSとか。

まとめ

ちゃんとGet-Help対応のヘルプ(Help Topic)を書こう。

*1:Linuxでいうmanコマンドで表示されるmanページのこと

*2:Linuxで例えるとmanのためのgroffマクロの書き方のようなイメージ。manページを書いたことはないけれど

VS Codeでgitを使うために認証情報を入力する

VS Codeでうまくgitが使えず、メッセージどこかに出てないかなーと思ったら、ふつうに出力タブが下にあってエラーメッセージに解決内容まで載っていた。

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

要するに認証を通せと言っているので、素直にターミナルでコマンド実行すると解消する。

参考

Visual Studio Code で Git を 使う | 験なきものを思はずは

PowershellでExcelファイルのシート名の一覧を取得したい(作りかけ2)

PowershellでExcelファイルのシート名の一覧を取得したい(作りかけ) - mk_55's diaryの続き。

とりあえず使えるようにはなった。あとはPesterでテスト書いたりする必要あり。いまだにVS Codeでgitが上手く使えない...

Excel.psm1

function Get-WorkSheetNames {
    [CmdletBinding()] #Write As a ScriptCmndlet
    Param (
        [parameter(mandatory = $true, ValueFromPipeline = $true)]
        [string[]]$Path,

        # todo need more better naming
        [parameter(mandatory = $false)]
        [boolean]$needsOutputFileName = $false
    )

    begin {
        # open Excel
        $excel = New-Object -ComObject Excel.Application
        $excel.Visible = $false
        $excel.DisplayAlerts = $false

    }

    process {
        try {
            # Argument Validation
            if (-not(Test-Path $Path -Include "*.xlsx", "*.xls", "*.xslm" )) {
                Write-Debug "File not Found or File not ExcelBook"
                throw  New-Object "System.ArgumentException" @("File not Found or not ExcelBook", $Path) -ErrorAction Stop
            }

            # Get absolute path (for Excel.Application.Workbooks.Open())
            $ExcelPath = Convert-Path $Path
            
            # Get sheet name
            $book = $excel.Workbooks.Open($ExcelPath)
            if ($needsOutputFileName) {
                $fileName = Split-Path $ExcelPath -Leaf
                $book.WorkSheets | ForEach-Object {Write-Host $filename ":" $_.Name }
            } else {
                $book.WorkSheets | ForEach-Object { $_.Name }
            }
            
        
        } catch [System.ArgumentException] {
            Write-Error $_

        } catch {
            # in order to close Excel in End Block
            Write-Errot $_
        
        } finally {
            # close book
            if ($book) {
                [void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($book)
            }
        }
    }

    end {
        # close Excel
        Write-Debug "Close Excel"
        [void]$excel.Quit()
        [void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
        
    }
}

Export-ModuleMember -Function Get-WorkSheetNames