invoke-commandのScriptBlockをMySQLから読み込み実行させてみたときのメモ

パーソナルなIT雑務を想定して、Windowsリモートホストに定型的なコマンドをmysqlから読み込み発行するシナリオでスクリプトを書いてみた。

Table Schema

oid(operation id)はscriptをグループ化させる想定で追加してある(not null制約がないのは失敗)。

mysql> desc operation;
+--------+---------------+------+-----+---------+-------+
| Field  | Type          | Null | Key | Default | Extra |
+--------+---------------+------+-----+---------+-------+
| id     | int           | NO   | PRI | NULL    |       |
| host   | varchar(128)  | YES  |     | NULL    |       |
| uid    | varchar(128)  | NO   |     | NULL    |       |
| oid    | int           | YES  |     | NULL    |       |
| script | varchar(1024) | YES  |     | NULL    |       |
| secure | varchar(1024) | YES  |     | NULL    |       |
+--------+---------------+------+-----+---------+-------+
6 rows in set (0.02 sec)

データ検索はpowershellでは少々、厳しそうだったので素直にpython scriptに任せた。
python scriptは結果をdictの配列で返し、powershell側でjson形式の文字列をconvertfrom-jsonでPSCustomObjectに変換する。

Python Script
import MySQLdb
import sys
import os

av = sys.argv
if len(av) < 2:
    print('oid is Mandatory')
    exit()
else:
    oid = av[1]

mysql_user   = os.environ['mysql_user']
mysql_passwd = os.environ['mysql_passwd']
computername = 'localhost'
dbname = 'db1'

conn = MySQLdb.connect(user=mysql_user,passwd=mysql_passwd,host=computername,db=dbname)
cur = conn.cursor()

placeholder = "select * from operation where oid = {} order by id"
sql = placeholder.format(oid)
cur.execute(sql)
rows = cur.fetchall()
conn.close()

keys = ['id','host','uid','oid','script','secure']
arr = []
for row in rows:
    d = {}
    for k,v in zip(keys,row):
        d[k] = v
    arr.append(d)

print(arr)


リモートホストには invoke-command で発行するが、scriptblock はべた打ちのときは { } の中にコマンドの文字列を書き込めばよい。しかし、コマンド文字列を変数に格納している場合は、{ }の中にコマンドの文字列を表示させても失敗する。
型変換はできないので、[Scriptblock]::Create()関数で文字列からscriptblockを生成しなければならない。
OperationIDは'ValueFromPipeline=$True'としてパイプラインから受け取れるようにしている、必要性はないのだが・・・。なお、不要でもbegin{},end{}を入れているのはadvanced functionを書く時の癖としている。

PowerShell Script
#
# Invoke Operation  
# e.g. invoke-operation -operationid 10
#
Function Invoke-Operation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True,
                   ValueFromPipeline=$True)]
        [ValidateNotNullOrEmpty()]
        [Int]$OperationID
    )
    begin {
    }
    process {
        $cmd = "python '${home}\Documents\Python Scripts\get_data_mysql.py' $OperationID"
        $ret = ( invoke-expression  $cmd )
        $ops = ( convertfrom-json $ret )
        
        foreach ( $h in $ops ){
            $pcname = ${h}.host
            $uid    = ${h}.uid
            $cmdstr = ${h}.script
            $cmdstr
            $script = [Scriptblock]::Create($cmdstr)
            $secure = ${h}.secure
        
            $pw = ConvertTo-SecureString -String $secure
        
            $c = New-Object System.Management.Automation.PSCredential($uid,$pw)
            invoke-command  -computername $pcname -credential $c -scriptblock $script
        }
    }
    end {
    }
}

ConvertFrom-SecureStringでセキュリティで保護された文字列 (System.Security.SecureString) を暗号化された標準文字列 (System.String) に変換して格納してあるので、ConvertTo-SecureStringで元に戻す。
ネットで調べると以下の関数で平文への復号が可能なようなので単なる気休めに過ぎないのかもしれない。しかし、結構、気休めになるのでは。

  • [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR()
  • [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR()

AsJobオプションを使用する場合は以下が参考になるかも。私には読み込む気力はなかったが・・・
www.vwnet.jp

script data
mysql> select oid,script from operation;
+------+---------------------------------------------------------------------------
| oid  | script                                                                    
+------+---------------------------------------------------------------------------
|   11 | docker container start python1                                      
|   10 | $c = docker container ls -aq -f name=test-.+; docker container start $c   
|   20 | docker container ls --format="table{{.ID}}     {{.Names}}      {{.Ports}}"
|   30 | 1..3 | % { $n = 'test-' + $_;$n;$c = docker container ls -q -f name=$n;...
|   50 | $c = docker container ls -q; docker container stop $c                    
|   90 | stop-computer -force                                                     
|   99 |                                                                          
+------+--------------------------------------------------------------------------
execution example
PS >invoke-operation -operationid 10
$c = docker container ls -aq -f name=test-.+; docker container start $c
d57707775ada
4b4ca2b65be2
b76c836b59fc
PS >invoke-operation -operationid 20
docker container ls --format="table{{.ID}}	{{.Names}}	{{.Ports}}"
CONTAINER ID   NAMES     PORTS
d57707775ada   test-3    0.0.0.0:8093->80/tcp
4b4ca2b65be2   test-2    0.0.0.0:8092->80/tcp
b76c836b59fc   test-1    0.0.0.0:8091->80/tcp
PS >invoke-operation -operationid 30
1..3 | % { $n = 'test-' + $_;$n;$c = docker container ls -q -f name=$n; docker container exec $c hostname -i }
test-1
172.17.0.4
test-2
172.17.0.3
test-3
172.17.0.2
PS >invoke-operation -operationid 50
$c = docker container ls -q; docker container stop $c
d57707775ada
4b4ca2b65be2
b76c836b59fc
PS >invoke-operation -operationid 90
stop-computer -force