はじめに

PowerShell の小ネタ集その 2 です。かなり雑多な内容になっています。

1. 自動変数を除き、自分で設定した変数だけを一括削除する
2. 自分のグローバル IP を取る
3. テンポラリでコマンド実行して終わったらクリーンアップする
4. コマンドラインからリファレンスページを開く
5. コマンドラインから AWS の What’s New Feed を検索する

1. 自動変数を除き、自分で設定した変数だけを一括削除する

PowerShell はセッション開始時に以下のように自動変数を設定します。以下は macos で profile を特に設定せずに Get-Variable した例で、pwsh は 7 系です。

Name                           Value
----                           -----
?                              True
^
$
args                           {}
ConfirmPreference              High
DebugPreference                SilentlyContinue
EnabledExperimentalFeatures    {}
Error                          {}
ErrorActionPreference          Continue
ErrorView                      ConciseView
ExecutionContext               System.Management.Automation.EngineIntrinsics
false                          False
FormatEnumerationLimit         4
HOME                           /Users/user1
Host                           System.Management.Automation.Internal.Host.InternalHost
InformationPreference          SilentlyContinue
input                          System.Collections.ArrayList+ArrayListEnumeratorSimple
IsCoreCLR                      True
IsLinux                        False
IsMacOS                        True
IsWindows                      False
MaximumHistoryCount            4096
MyInvocation                   System.Management.Automation.InvocationInfo
NestedPromptLevel              0
null
OutputEncoding                 System.Text.UTF8Encoding
PID                            17621
PROFILE                        /Users/user1/.config/powershell/Microsoft.PowerShell_profile.ps1
ProgressPreference             Continue
PSBoundParameters              {}
PSCommandPath
PSCulture                      en-US
PSDefaultParameterValues       {}
PSEdition                      Core
PSEmailServer
PSHOME                         /usr/local/microsoft/powershell/7
PSNativeCommandArgumentPassing Standard
PSScriptRoot
PSSessionApplicationName       wsman
PSSessionConfigurationName     http://schemas.microsoft.com/powershell/Microsoft.PowerShell
PSSessionOption                System.Management.Automation.Remoting.PSSessionOption
PSStyle                        System.Management.Automation.PSStyle
PSUICulture                    en-US

スクリプトを書くに当たっていろいろ試行錯誤していると、その過程で一時的な変数を設定したり、別名で変数を作り直したり、そんな感じでいつのまにかゴミが溜まってしまうことはよくあります。

そうなると、自動変数を除いた自作変数だけを瞬間的に一括削除したいなぁと考えはじめます。しかし PowerShell にそんな機能はありません。今回はそれを私なりに実現した方法です。

profile を作成する

これは profile による PowerShell セッション開始時のスクリプト読み込みで実現できます。まずは profile.ps1 を作成します。

if (-not (Test-Path -LiteralPath $profile.CurrentUserAllHosts)) {
    New-Item -Path $profile.CurrentUserAllHosts -ItemType File
}

profile を編集する

profile.ps1 に以下を記述します。

# Capture the variable state at terminal startup
New-Variable -Name '__defaultVars' -Value $(
  (
    (Get-Variable).Name.ForEach{
      if ($_ -in '?', '^', '$') {
        '`' + $_
      }
      else {
        $_
      }
    } + '__defaultVars'
  ) | Sort-Object
) -Option Constant

このように書くと、PowerShell セッション開始時の profile 読み込みで概ね以下のような処理が動きます。

  1. セッション開始時に自動設定された変数の名前一覧をエスケープ処理しながら変数 __defaultVars に代入
  2. 変数 __defaultVars__defaultVars (自分自身の変数名) を追加
  3. -Option パラメータに Constant を渡して定数化し、勝手に削除されないようにする

これで、セッション開始時の変数名一覧がキャプチャされました。あとはこれを使う関数を profile に追記し、読み込ませればよいです。

  • セッション開始以降に設定された変数を一覧表示する関数
# List variables set after the terminal is ready
function global:gva {
  Get-Variable -Name * -Exclude $__defaultVars -Scope Script
}
  • セッション開始以降に設定された変数を削除する関数
# Delete variables set after the terminal is ready
function global:rva {
  Remove-Variable -Name * -Exclude $__defaultVars -Scope Script
}

これで「自動変数を除き、自分で設定した変数だけを一括削除したい」が達成できました。-ErrorAction Ignore でエラーを握りつぶすようなこともないため、比較的クリーンな手法で実現できたといえます。

ただ注意点として、VSCode などツールの設定によってはそちら側で独自に設定した変数などがあり、これが削除時に予期せぬ挙動をしたりするので、削除対象から除外するなど対応が必要になるかもしれません。

PowerShell だと bash における exec $SHELL -l のようなことを直接は実現できないので、こういうちょっとしたクリーンアップ用コマンドも割と使うことがあります。

(以下コマンドで、現在のセッションの中で新しいセッションを起動することはできるのですが、PowerShell セッションが自分自身を再起動するような方法はない気がします。知っている方がいれば教えてほしいです)

Invoke-Command -ScriptBlock { & pwsh } -NoNewScope

ニッチすぎて誰得なネタですが、供養のために紹介しました。

2. 自分のグローバル IP を取る

上記に比べ、簡単かつ実用的なネタです。checkip.amazonaws.com のようなサイトに curl 的なコマンドを実行する関数を profile に登録しておけば、調べる手間が省けます (gip と打ちさえすればよい)

function gip {
  param (
    [Alias('c')]
    [switch]$cidr
  )

  $ret = (Invoke-RestMethod -Uri 'checkip.amazonaws.com' -Method Get).Trim()

  if ($cidr) {
    $ret = '{0}/32' -f $ret
  }

  return $ret
}

3. テンポラリでコマンド実行して終わったらクリーンアップする

特定のコマンドを TMPDIR 配下などで実行して、終わったらその痕跡をすぐ削除したいようなケースが稀によくあります。PowerShell で関数化して、引数でスクリプトブロックを渡せるようにしておけば、テンポラリでの作業を少し楽にできます。

#Requires -Version 6.0

function usetemp {
  param (
    [Alias('c')]
    [ScriptBlock]$command
  )

  $tmpDir = if ($IsWindows) {
    $env:TMP
  }
  else {
    $env:TMPDIR
  }

  # Create temporary directory
  $tmpSubDir = $(New-Item -Path $tmpDir -Name $([System.Guid]::NewGuid().Guid) -ItemType Directory).FullName

  try {
    # Change temporary directory
    Push-Location -LiteralPath $tmpSubDir
    Write-Host $('Working in ''{0}''' -f $tmpSubDir) -ForegroundColor Cyan

    # Execute command
    $result = Invoke-Command -ScriptBlock $command
    return $result
  }
  catch {
    throw $_.Exception
  }
  finally {
    # Cleaning
    Pop-Location
    Remove-Item -LiteralPath $tmpSubDir -Recurse -Force
  }
}

使い方

usetemp -command { New-Item -Path "test.txt" -ItemType File | Get-ChildItem }

大事な点

  • 一時フォルダ名が衝突しないように GUID を使う
  • finally ブロックで後始末

4. コマンドラインからリファレンスページを開く

なにかのコマンドリファレンスを見たいが、ブラウザ操作でリファレンスを検索して該当のコマンドまでたどり着くのが面倒という場合に、目的のページにコマンドラインから直行する方法です。例として AWS Tools for PowerShell のリファレンスを対象とします (ちなみに AWS Tools for PowerShell のインストール方法はコチラ)

まず、引数でコマンド名を渡すときのコマンド一覧が TAB 補完で選択できるようになってほしいので、その設定をします。

# Get-EC2Instance などコマンドレット一覧が補完されてほしい
awspwshref -command Get-EC2Instance

この補完の設定は、System.Management.Automation.IValidateSetValuesGenerator インターフェースを実装することで実現できます。GetValidValues メソッドでの戻り値が補完で表示される値になります。

#Requires -Version 6.0

class AWSPowerShellCommand : System.Management.Automation.IValidateSetValuesGenerator {
  [string[]]GetValidValues() {
    return (Get-AWSCmdletName).CmdletName
  }
}

あとは以下のようにして、リファレンスの URL に引数で得たコマンド名を埋め込んでブラウザオープンする関数を書けばよいです。補完は param() 内のパラメータに対して [ValidateSet([])] を付与することで設定できます (oscmd はこちらで紹介した自作関数です)

function awspwshref {
  param(
    [ValidateSet([AWSPowerShellCommand])]
    [Alias('c')]
    [string]$command
  )

  $uri = 'https://docs.aws.amazon.com/powershell/latest/reference/items/{0}.html' -f $command

  $commands = @{
    'windows' = { cmd.exe /c "start """" ""$uri""" }
    'macos' = { open $uri }
    'linux' = { echo "no browser for gui!" }
  }

  oscmd @commands
  return
}

これで、あるコマンドのリファレンスページをブラウザで開く一連の作業が一撃化できました。

5. コマンドラインから AWS の What’s New Feed を検索する

これもニッチすぎるネタなのですが、AWS の What’s New Feed でリリース情報を調べたいとします。弊社ではこの RSS フィードを垂れ流す slack チャンネルがあり活用していたのですが、なかなか能動的に見に行かなくなってしまいました。これを簡易的な方法でオンザフライに調べたいなぁという時に、コマンドラインで検索ワードを入力してそのままブラウザを開きます。

function awsfeed {
  param (
    [Alias('w')]
    [string[]]$word
  )

  $words = $word -join '%2B'
  $uri = (
    '{0}?{1}' -f 'https://aws.amazon.com/new/', (
      @(
        'whats-new-content-all.sort-by=item.additionalFields.postDateTime'
        'whats-new-content-all.sort-order=desc'
        'awsf.whats-new-categories=*all'
        'whats-new-content-all.q={0}'
        'whats-new-content-all.q_operator=AND#What.27s_New_Feed'
      ) -join '&'
    )
  ) -f $words

  $commands = @{
    'windows' = { cmd.exe /c "start """" ""$uri""" }
    'macos' = { open $uri }
    'linux' = { echo "no browser for gui!" }
  }

  oscmd @commands
  return
}

まず引数で渡した複数の文字列を、’+’ を URL エンコードした値である ‘%2B’ で連結します。

$words = $word -join '%2B'

次に URL とクエリパラメータを組み立てます。

  $uri = (
    '{0}?{1}' -f 'https://aws.amazon.com/new/', (
      @(
        'whats-new-content-all.sort-by=item.additionalFields.postDateTime'
        'whats-new-content-all.sort-order=desc'
        'awsf.whats-new-categories=*all'
        'whats-new-content-all.q={0}'
        'whats-new-content-all.q_operator=AND#What.27s_New_Feed'
      ) -join '&'
    )
  ) -f $words

クエリパラメータは以下の構造になっています。

パラメータ 意味
whats-new-content-all.sort-by=item.additionalFields.postDateTime postDateTime でソート指定
whats-new-content-all.sort-order=desc 降順でソート指定
awsf.whats-new-categories=*all 全カテゴリを指定
whats-new-content-all.q={0} $words に格納されている検索文字列を指定
whats-new-content-all.q_operator=AND 検索条件としてANDオペレータを指定

また、以下でアンカーを指定して画面上 What’s New Feed の位置にジャンプします。

#What.27s_New_Feed

あとはクエリパラメータを & で連結し、ベース URL とクエリパラメータの塊を ? で連結して URL を作り、OS ごとのコマンドでブラウザを開きます (oscmd はこちらで紹介した自作関数です)

$commands = @{
  'windows' = { cmd.exe /c "start """" ""$uri""" }
  'macos' = { open $uri }
  'linux' = { echo "no browser for gui!" }
}

oscmd @commands

おわりに

コンテキストとしては PowerShell を活用した業務効率化かなと思いながらこの記事を書いていました。
毎日のようにターミナルを開くので、いろんな操作をコマンドで実行できるようにしておくと捗ります。楽がしたいモチベーションで調査をする中で技術力も向上できるので、こういう細かいことに拘るのはいいことだと思いました。