【未解決】PHPのDirectoryIterator::isDotの理解できない挙動についてのメモ
Windows10 HOMEのPCでPHPのDirectoryIteratorでディレクトリの中のファイルやディレクトリを列挙するコードを書いていた時、理解できない現象に遭遇したことについてメモしておく。
'.'や'..'は表示して欲しくないので、最初はisDotで判定しようとした。
<?php if ( isset($_GET['dir']) ){ $dir = $_GET['dir']; } else { $dir = './'; } $this_page = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://').$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; $iterator = new DirectoryIterator($dir); foreach($iterator as $file){ if ($file->isFile()){ $pathname = $file->getPathname(); print "<li><a href='{$pathname}'>{$pathname}</a></li>"; } } foreach($iterator as $file){ if ($file->isDir()) { $pathname = $file->getPathname(); $sub1 = substr($pathname,-2); $sub2 = substr($pathname,-3); // if ( !file->isDot() ){ if( $sub1 != '\.' && $sub2 != '\..' ){ print "<li><a href='{$this_page}?dir={$pathname}'>{$pathname}</a></li>"; } } }
ファイルなどは以下の通りだが、なぜか、isDot()を使ったときは'dir11'と'dir12'が表示されなくなってしまう。
ディレクトリ: C:\WebApplication\php\Documents\dir1 Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2022/11/08 11:19 dir11 d----- 2022/11/08 11:20 dir12 -a---- 2022/11/08 11:18 26 doca.txt
仕方なく部分文字列で判定するように変更したのだが・・・。
なぜ、'dir11'と'dir12'とはisDotなのだろう?
了
Let's Try Qiita API via curl of Ubuntu 20.04(WSL)
Let's Try Qiita API via curl of Ubuntu 20.04(WSL)
WSL(Ubuntu 20.04)のcurlでQiita APIを使ってみた。
powershellのときはpowershellのscriptだけで済んだが、
Let's try Qiita API v2 via curl of powershell - willwealth’s diary
シェルでJSONを扱うのは厳しそうだったので、外側にpythonをかぶせ python でdictに変換して処理した。
qiita.sh
#!/bin/bash url='https://qiita.com/api/v2/items?page=1&per_page=10' curl $url
powershellのcurlはcontentをStringで返してきたが、このケースでは'bytes'で返される。
>>> res = subprocess.run(['/home/user1/qiita.sh'],capture_output=True) >>> bs = res.stdout >>> type(bs) <class 'bytes'>
そこで、'bytes'をdecode()で'str'に変換してからjson.loads()する。
JSON文字列をdictに変換するloads()とは別に、ファイルからdictに変換するload()という関数があり躓いてしまった。
初心者からするとちょっと紛らわしかったので注意が必要だ。
qiita.py
#!/usr/bin/python3 import subprocess import json res = subprocess.run(['/home/user1/qiita.sh'],capture_output=True) bs = res.stdout str = bs.decode() arr = json.loads(str) for i,a in enumerate(arr): title = a['title'] print(f"[{i}] {title}")
了
invoke-commandのcomputernameにpowershellの配列を渡してみた!ただ、Get-Hostの挙動がちょっと変?
invoke-commandはcomputernameを配列で指定できる。
以下のリンクの「例 7: 複数のコンピューター上のホスト プログラムのバージョンを取得する」を試してみた。
$version = Invoke-Command -ComputerName (Get-Content Machines.txt) -ScriptBlock {(Get-Host).Version}
invoke-commandの-computernameは配列を受け取ってくれるのか試してみたら、本当に受け取ってくれた。
(credencialは一致している前提で)
ただ、Get-Hostの結果がlocalhostで実行した場合と異なっていた。
invoke-commandだとversionが全て1で同じになってしまう。
なぜだろう?
’[system.environment]::osversion’で試してみたら異なる結果が得られた。
PS >(get-host).version Major Minor Build Revision ----- ----- ----- -------- 5 1 14409 1027 PS >$h = ( get-content .\hosts.txt ) PS >$h PCXX PCYY localhost PS >invoke-command -computername $h -scriptblock { (get-host).version } Major Minor Build Revision PSComputerName ----- ----- ----- -------- -------------- 1 0 0 0 localhost 1 0 0 0 PCXX 1 0 0 0 PCYY PS >invoke-command -computername $h -scriptblock { [system.environment]::osversion } PSComputerName : PCXX RunspaceId : ********** Platform : Win32NT ServicePack : Version : 10.0.22621.0 VersionString : Microsoft Windows NT 10.0.22621.0 PSComputerName : localhost RunspaceId : *********** Platform : Win32NT ServicePack : Version : 6.3.9600.0 VersionString : Microsoft Windows NT 6.3.9600.0 PSComputerName : PCYY RunspaceId : ********** Platform : Win32NT ServicePack : Version : 10.0.19044.0 VersionString : Microsoft Windows NT 10.0.19044.0
PSRemotingでget-hostを実行しても同様だったので、実行するcmdletによっては注意が必要ということだろうか。
それよりも、powershell自体が古いバージョンだということが問題なのかもしれないが、もうすぐ退役なので・・・。
Name Value ---- ----- PSVersion 5.1.14409.1027
了
【確認】PSRemotingやinvoke-commandで管理者権限が必要なcmdletを試す
localhostで管理者powershellを起動するときはユーザーアカウント制御が作動し、
「このアプリがデバイスに変更を加えることを許可しますか」と聞いてくる。
PSRemotingやinvoke-commandはどうなっているのだろうと気になり試してみた。
【結論】
- PSRemotingでは管理者として認知してくれる
- invoke-commandでも管理者として認知してくれる
get-websiteは管理者権限が必要なため、管理者権限を持っていても通常(localhost)のpowershellでは実行できない。
PS >get-website 'default web site' PS >終了エラー(Get-Website): "名前 'WebAdministration' を持つプロバイダーが見つかりません。" get-website : 名前 'WebAdministration' を持つプロバイダーが見つかりません。 発生場所 行:1 文字:1 + get-website 'default web site' + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (WebAdministration:String) [Get-Website], ProviderNotFoundException + FullyQualifiedErrorId : ProviderNotFound,Microsoft.IIs.PowerShell.Provider.GetWebsiteCommand get-website : 名前 'WebAdministration' を持つプロバイダーが見つかりません。 発生場所 行:1 文字:1 + get-website 'default web site' + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (WebAdministration:String) [Get-Website], ProviderNotFoundException + FullyQualifiedErrorId : ProviderNotFound,Microsoft.IIs.PowerShell.Provider.GetWebsiteCommand
勿論、管理者権限のpowershellからは実行できるが、管理者権限のpowershellを起動するにはGUI操作が必要になる。
一方、
PSRemotingでは管理者権限者として認知してくれているようだった。
[PCXXX]: PS C:\Users\user1\Documents> get-website 'default web site' Name ID State Physical Path Bindings ---- -- ----- ------------- -------- Default Web Site 1 Stopped %SystemDrive%\inetpub\wwwroot http *:80:
invoke-commandでも管理者権限者として認知してくれているようだった。
PS >invoke-command -computername PCXXX -scriptblock { get-website 'default web site'} Name ID State Physical Path Bindings PSComputerName ---- -- ----- ------------- -------- -------------- Default Web Site 1 Stopped %SystemDrive%\inetpub\wwwroot http *:80: PCXXX PS >invoke-command -computername PCXXX -scriptblock { start-website 'default web site'} PS >invoke-command -computername PCXXX -scriptblock { get-website 'default web site'} Name ID State Physical Path Bindings PSComputerName ---- -- ----- ------------- -------- -------------- Default Web Site 1 Started %SystemDrive%\inetpub\wwwroot http *:80: PCXXX
PSRemotingではGUI操作はできないので管理者ユーザーが管理者権限で実行できるのは当然なのかもしれない。
localhostでのハードルが高いのは、たぶんユーザーへの安全性への配慮なのだろう。
余談
invoke-commandはlocalhostのscript fileを指定して実行することもできる。
PS >invoke-command localhost -filepath D:\Learning\powershell\qiita.ps1
qiita.ps1
$url = 'https://qiita.com/api/v2/items?page=1&per_page=20' $r = curl -URI $url $c = ($r.content | convertfrom-json ) $i = 0 foreach ( $a in $c ){ Write-Host "[${i}]" ${a}.title $i ++ }
関数を実行することもできるが、
PS >invoke-command localhost -scriptblock $function:testfunc 2022年10月22日 11:24:31
関数名に'-'が含まれている場合や
PS >invoke-command localhost -scriptblock $function:test-function -argumentlist"2" Invoke-Command : パラメーター 'ScriptBlock' をバインドできません。"-function" の値を "System.String" 型から "System.Management.Automation.ScriptBlock" 型に変換できません。 ...
引数を必要とする関数の場合のやり方が分からず諦めた。
PS >invoke-command localhost -scriptblock $function:testfunction -argumentlist"2" Invoke-Command : パラメーター 'ScriptBlock の引数を確認できません。引数が null です。引数に有効な値を指定して、コマンドを再度実行してください。 ...
了
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
了
PowerShellで文字列から[ScriptBlock]::Create()関数でScriptBlockを作ったときのメモ。
今まで invoke-command のscriptblockはベタ打ちでしか使ったことがなかったが、
「PSRemotingでDockerのコンテナの中に入れないのであれば、invoke-commandとさほど変わらないのでは?」
と思い、scriptblockの文字列を変数化してinvoke-commandを試してみて躓いてしまった。
そのとき以下の記事が参考になった。
yomon.hatenablog.com
べたで打ってるときと同じ調子で、コマンドの文字列を変数に格納し、{$cmdstr}としてみたが駄目だった。
[System.Management.Automation.ScriptBlock]型に変換もできない。(前後してしまうが・・・)
PS >$t = [System.Management.Automation.ScriptBlock]$c "{date}" の値を "System.String" 型から "System.Management.Automation.ScriptBlock" 型に変換できません。 発生場所 行:1 文字:1 + $t = [System.Management.Automation.ScriptBlock]$c + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (:) []、RuntimeException + FullyQualifiedErrorId : ConvertToFinalInvalidCastException
どうも、[Scriptblock]::Create()関数でちゃんと作らなければならないようだ。
PS >$c = 'date' PS >$s = [Scriptblock]::Create($c) PS >$s.gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True ScriptBlock System.Object PS >$s.invoke() 2022年10月9日 13:59:29 PS >$c = '{date}' PS >$s = [Scriptblock]::Create($c) PS >$s.gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True ScriptBlock System.Object PS >$s.invoke() date PS >$s.invoke().invoke() 2022年10月9日 14:00:41 PS >stop-transcript
コマンド文字列に'{ }'を入れてしまうと、'$s.invoke().invoke()'と2回ほどinvoke()しないと意図する結果が得られなかった。なので文字列としては'{ }'は付けない方が良いのだろう。
了
=============
2022-10-10 p.s.
ベタ打ちのときは文字化けしないが、[scriptblock]::create()で生成したscriptblockをinvokeするとなぜか文字化けする。
PS >$cmd = 'docker container ls -a -f name=test-.+' PS >$x = [scriptblock]::create($cmd) PS >$x.invoke() CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9bbd5b2256b6 nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 12 days ago Exited (0) 2 days ago test-3 34410af55b73 nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 12 days ago Exited (0) 2 days ago test-2 d892fccc011b nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 12 days ago Exited (0) 2 days ago test-1
一方、
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command "chcp 65001"
で起動したpowershellではencodingはus-asciiのままだったが、文字化けはしていなかった。
PowerShell起動時、文字コードをUTF-8に変える方法 - Qiita
なぜだろう?何が違うのだろう?
PS >$cmd = 'docker container ls -a -f name=test-.+' PS >$x = [scriptblock]::create($cmd) PS >$x.invoke() CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9bbd5b2256b6 nginx:1.17.6-alpine "nginx -g 'daemon of…" 12 days ago Exited (0) 2 days ago test-3 34410af55b73 nginx:1.17.6-alpine "nginx -g 'daemon of…" 12 days ago Exited (0) 2 days ago test-2 d892fccc011b nginx:1.17.6-alpine "nginx -g 'daemon of…" 12 days ago Exited (0) 2 days ago test-1 PS >$outputencoding IsSingleByte : True BodyName : us-ascii EncodingName : US-ASCII HeaderName : us-ascii WebName : us-ascii WindowsCodePage : 1252 IsBrowserDisplay : False IsBrowserSave : False IsMailNewsDisplay : True IsMailNewsSave : True EncoderFallback : System.Text.EncoderReplacementFallback DecoderFallback : System.Text.DecoderReplacementFallback IsReadOnly : True CodePage : 20127
eof
【整理】PSRemotingの制約をついつい忘れてしまうのでとりあえずメモ
PSRemotingを使っていると何かと躓くことが多いので、この際、リストアップしておこう。
- Enter-PSSessionはネストできない。
- WSLにsshできない。
- Dockerのコンテナの中に入ることができない。
Windows PowerShell PSSession の実行中は、Enter-PSSession コマンドレットを使用して別の PSSession を実行することはできない。
管理者権限powershellだとlocalhostにEnter-PSSessionできるが、さらにlocalhostにEnter-PSSessionしようとすると怒られる。
これは、異なるPC間でも同じであった。
PS >Enter-PSSession -computername localhost [localhost]: PS C:\Users\user1\Documents> Enter-PSSession -computername localhost Enter-PSSession : 現在 Windows PowerShell PSSession の実行中のため、Enter-PSSession コマンドレットを使用して別の PSSession を実行することはできません。 + CategoryInfo : InvalidArgument: (:) [Enter-PSSession]、ArgumentException + FullyQualifiedErrorId : RemoteHostDoesNotSupportPushRunspace,Microsoft.PowerShell.Commands.EnterPSSessionCommand
WSLの中に入れない
WSLを入れているPCにEnter-PSSessionしても、WSLにはsshで入ることはできない。
'Pseudo-terminal will not be allocated'と断られてしまうが、何やらメッセージは表示される。
[localhost]: PS C:\Users\user1\Documents> ssh 192.168.150.210 ssh : Pseudo-terminal will not be allocated because stdin is not a terminal. + CategoryInfo : NotSpecified: (Pseudo-terminal...not a terminal.:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.10.60.1-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sun Oct 2 15:52:34 JST 2022 System load: 0.06 Processes: 13 Usage of /: 1.0% of 250.98GB Users logged in: 0 Memory usage: 3% IPv4 address for eth0: 192.168.150.210 Swap usage: 0% 253 updates can be applied immediately. 182 of these updates are standard security updates. To see these additional updates run: apt list --upgradable New release '22.04.1 LTS' available. Run 'do-release-upgrade' to upgrade to it. [localhost]: PS C:\Users\user1\Documents>
Dockerのコンテナの中に入ることができない。
コンテナの起動・停止はできるが、exec -it で中に入ろうとすると'docker : the input device is not a TTY'と怒られる。
これも、きっとPseudo-terminalであることが制約になっているのだろう。
[localhost]: PS C:\Users\user1\Documents>>$c = docker container ls -q [localhost]: PS C:\Users\user1\Documents> docker container exec -it $c /bin/sh docker : the input device is not a TTY. If you are using mintty, try prefixing the command with 'winpty' 発生場所 行:1 文字:1 + docker container exec -it $c /bin/sh + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (the input devic...d with 'winpty':String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError [localhost]: PS C:\Users\user1\Documents>
'mintty'なるものを使っていれば可能性があるのかもしれないが、独立しているソフトではなく前提条件としてインストールしなければならないソフトがあるようなので追求するのは断念した。
コンテナの中に入れないのでは、面倒だが外からコマンドを発行するしかなさそうだ。
なお、起動・停止だけなら文字化けする程度でCONTAINER IDの配列を渡して問題なく実行できた。
[localhost]: PS C:\Users\user1> docker container ls -a -f name=test-.* CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9bbd5b2256b6 nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 5 days ago Exited (0) 3 days ago test-3 34410af55b73 nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 5 days ago Exited (0) 3 days ago test-2 d892fccc011b nginx:1.17.6-alpine "nginx -g 'daemon of窶ヲ" 5 days ago Exited (0) 3 days ago test-1 [localhost]: PS C:\Users\user1> $c = docker container ls -a --quiet -f name=test-.* [localhost]: PS C:\Users\user1> $c 9bbd5b2256b6 34410af55b73 d892fccc011b [localhost]: PS C:\Users\user1> docker container start $c 9bbd5b2256b6 34410af55b73 d892fccc011b
了