Powershellの「Write-Output」と「Write-Host」、結局どっちがいいの問題
まさかの2連更新です。
「Write-Output」と「Write-Host」の違い
Write-OutputとWrite-Hostの違いについては、別の方が詳しく説明されているので端折りますが、結局のところ使い分けが大事です。
・パイプライン処理をさせたり、結果を変数へ保存したい場合、Write-Output
・コンソール画面に文字を出したいだけなら、Write-Host
これに尽きます。
Write-Outputは本来、パイプライン処理を行う際に使うのでコンソール画面への表示は行いません。(ただし、特別な仕組みによって実際にはコンソール画面へ表示される)
対してWrite-Hostの場合、コンソール画面への出力を主目的としているので文字に色を付けて(-ForegroundColor)表示させたり、文字の背景色を指定する(-BackgroundColor)こともできます。
注意点として、あまりありませんが「Write-Hostの結果を変数に保存する」ことはできません。
ex)
Wirte-Host の場合、結果を変数へ保存することはできない
Write-Output の場合、結果を変数へ保存できる
なので、コンソール画面へ文字を出力したいだけであれば「Write-Host」を、結果を保存して後々利用する場合は「Write-Output」を利用しましょう。
.ps1ファイルを管理者権限で実行する
前回の記事で
「次回以降はオススメBTOパソコンを紹介する」
と言いましたが、先に書きたいことができましたので次回以降で!
.ps1ファイルを管理者権限で実行したい
.ps1ファイルを管理者権限で実行するには、大きく分けて2パターンあります。
- batファイルから管理者権限へ昇格させて.ps1を実行する
- スクリプト内に管理者権限へ昇格させるコードを記述する
1.の場合はbatファイルを別に用意しなければならないので、配布するファイル数が多くなってしまいます。
配布ファイルサイズを削減したい方にはあまり向きません。更に2.の方法と比べるとあまりメリットもありません。
なので今回は2.の方法。
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators")){ Start-Process powershell.exe "-File `"$PSCommandPath`"" -Verb RunAs exit }
解説します。
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators")){
「[Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()」は、PowerShellを実行している権限を取得しています。
さらに後ろに「.IsInRole("Administrators")」を付けることにより、ユーザーの所属しているグループを判定しています。ちなみに、含まれている場合は「True」が返ります。
簡単に説明すると、「今.ps1を実行している権限が「Administratorsグループ」でなければ という意味です。
Start-Process powershell.exe "-File `"$PSCommandPath`"" -Verb RunAs exit }
「Start-Process powershell.exe」でPowershellを新しいプロセスとして起動しています。
その後ろについているオプション「"-File `"$PSCommandPath`""」は、起動しているファイル自身を指定するコマンドです。
「-Verb RunAs」は、管理者として という意味になります。
つまり、「自分自身を新しいプロセスで、管理者として実行する」という意味になり、最初のものと合わせると
「.ps1を実行したユーザーの権限が管理者権限でなければ、自分自身を管理者権限へ昇格させて新しいプロセスとして実行する」という意味になります。
たった数行で.batを用意することもなく、簡単に管理者権限へ昇格させてから実行できるようになるので、作ったスクリプトファイルの先頭へ記述することをオススメします。
CrashHandlerを強制終了させる
皆さんお久しぶりです。
最近ネタがなく、更新できませんでした。
CrashHandlerが気持ち悪い
ウマ娘プレイ時やGoogle Driveを利用している際、まれに「CrashHandler」というプロセスが起動していることがあります。
そのまま放置していても害があるわけではありませんが、私は見知らぬプロセスが起動していると気持ち悪く感じます。
そこで今回は、この「CrashHandler」と名の付くプロセスを終了させるPowerShellプログラムを作ります。
ソースコード
# ポリシーの変更 Set-ExecutionPolicy RemoteSigned -Scope Process -Force # プロセス一覧を取得し、Foreachで処理させる Get-Process | ForEach-Object { # 上記で取得したプロセス名に拡張子がついていないので、拡張子をつける $process_name = $_.ProcessName + ".exe" # もし $process_nameに"crash"と一致する文字列がある場合 if($process_name -like "*Crash*"){ # $process_name(プロセス名)を強制終了させる taskkill /f /im $process_name } }
たったこれだけです。
やってる事も簡単ですし、私にしては珍しくコメントを記載しました。
これで、ウマ娘やGoogleDriveだけでなく、他のプログラムで発生した「CrashHandler」も強制終了させてくれます。
注意点としては、$process_name内に「crash」という文字列が含まれている場合、それを強制終了させるので
「CrashHandler」でない「crash」と名のつくプロセスまで終了させてしまいます。(例えばクラッシュバンディクーとか。PC版あるのか、はたまたプロセス名に「crash」が含まれるのかわかりませんが)
その場合はソースコードを下記のように変更すれば問題ありません。
# ポリシーの変更 Set-ExecutionPolicy RemoteSigned -Scope Process -Force # プロセス一覧を取得し、Foreachで処理させる Get-Process | ForEach-Object { # 上記で取得したプロセス名に拡張子がついていないので、拡張子をつける $process_name = $_.ProcessName + ".exe" # もし $process_nameに"crash"と一致する文字列がある場合 if($process_name -like "*Crash*"){ # 終了させたくないプロセスを下記で指定 if($process_name -like "任意のプロセス名"){ continue } # $process_name(プロセス名)を強制終了させる taskkill /f /im $process_name } }
これで終了させたくないプロセスは終了されません。
また、このプログラムを常駐し、1分毎にプロセスを取得、「CrashHandler」があれば終了させるようにしたい場合は
# ポリシーの変更 Set-ExecutionPolicy RemoteSigned -Scope Process -Force while(1){ Start-Sleep -s 60 # プロセス一覧を取得し、Foreachで処理させる Get-Process | ForEach-Object { # 上記で取得したプロセス名に拡張子がついていないので、拡張子をつける $process_name = $_.ProcessName + ".exe" # もし $process_nameに"crash"と一致する文字列がある場合 if($process_name -like "*Crash*"){ if($process_name -like "任意のプロセス名"){ continue } # $process_name(プロセス名)を強制終了させる taskkill /f /im $process_name } } }
で問題ありません。
今回の更新は短いですが、以上となります。
次回以降はオススメなBTOパソコンの構成など書きたいと思います。
Powershellで作成されたプログラムを動かそうとすると怒られる
Powershell Policyの状態を確認しよう
皆さんはPowershellを利用していますか?
Windowsに標準で備わっている機能であり、構文も難しくなくリファレンスも豊富で大変便利な代物です。
しかし、Powershellを勉強しようとした方が最初に躓くのがポリシー関連。
Powershell ISEでスクリプトを実行しようとしてもエラー、ps1を直接叩いてもエラー。
それもそのはず、Powershellは外部プログラムを呼び出して動作させたり、引数に暗号化した悪意たっぷりのコマンドを投げ、別のps1スクリプトで復号->実行
なんてこともできてしまいます。
そうならないために初期状態のPowershellはポリシーがガッチガチです。
試しにPowershellで下記のコマンドを実行してみてください。
Get-ExecutionPolicy -List
Powershellを初めて利用するPCではほとんどがRestrictedになってるかと思われます。
これ、個別のコマンド(Powershellのプロンプト上から1個1個コマンドを入力すること)は許可しますが、ps1などのスクリプトは許可しない設定のことです。
そしてRestricted以外にも複数の状態があります。
Policyの状態
状態 | 説明 |
---|---|
Restricted | スクリプトは全て実行不可 |
AllSigned | 署名付きスクリプトのみ実行可能 |
RemoteSigned | |
Bypass | 何もブロックせず、警告もプロンプトも表示しないで実行 |
Unrestricted | 全て実行可能 |
Undefined | 未定義(Restrictedのポリシーが適応される) |
つまり、初期状態だとスクリプトの実行は全て弾かれるんです。
ポリシーを変更しよう
では、これからPowershellでプログラミングを勉強する方のためにもポリシーを変更しましょう。
下記のコマンドを実行予定のスクリプトかプロンプトに入力してください。
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
これでスクリプトの実行が許可されるようになりました。
でもなんだか見慣れない引数がありますね。
ご安心ください。説明します。
最初に
Get-ExecutionPolicy -List
を実行してもらったときに表のような形で出力されたかと思います。
Scope | ExecutionPolicy |
---|---|
MachinePolicy | UnDefined |
UserPolicy | Undefined |
Process | Undefined |
CurrentUser | AllSigned |
LocalMachine | RemoteSigned |
それぞれの意味も説明します。
Scope(有効範囲)の説明
MachinePolicy | グループポリシーでそのコンピュータの全ユーザーに強制 |
---|---|
UserPolicy | 現在のユーザーに強制 |
Process | 現在のセッションに対して適応 |
CurrentUser | 現在のユーザーに対して適応 |
LocalMachine | コンピュータの全ユーザーに対して適応 |
つまり、先ほどのコマンドは
ローカルでのスクリプトの実行を許可する 有効範囲はスクリプトが終了するまで
という意味になります。
でもこれ、毎回スクリプトに記載するかプロンプトに入力しなければなりません。
正直面倒くさい。
ってことでPolicyのScopeを変更しましょう。
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
これで毎回入力する手間が省けます。
ただし、怪しいスクリプトをDLして実行してしまう危険性もありますのでご注意を。
最後に注意とスコープのオススメ設定
なお、Scopeパラメータを指定しないでポリシー変更コマンドを実行するとLocalMachineの実行ポリシーを変更することになり、管理者として実行していない場合はエラーになります。
また、グループポリシーによって優先度の高いMachinePolicyやUserPolicyスコープがRestrictedに設定されている場合は管理者権限で頑張ってもスクリプトを実行することができません。※batやvbsからスクリプトブロックとして実行することは可能
私個人の考えですが、どれだけ対策していてもウイルスに感染する可能性を0%にすることは不可能なので、少しでもリスクを避けるのであればセキュリティ重視の
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
にすることをオススメします。
PowershellでSeleniumを使う
PowershellでSeleniumを使いたい人、たんさんいらっしゃるかと思われますが、現在検索しても古い情報しか載っていないので私が躓いた点も交えながら詳細を書きます。
※Chromeのお話なのでEdgeやIE、FireFoxやOperaメインの方はごめんなさい。
記事作成段階の各ソフトウェアバージョンは下記の通りです。
・Powershell - 5.1.19041.1320
・Google Chrome - 97.0.4692.99(Official Build) (64 ビット)
・前提 - ダウンロード
PowershellでSeleniumを使う際は必ず以下の3点が必要です。
①WebDriver
www.selenium.dev
②WebDriverSupport.dll
www.nuget.org
※上記2つは拡張子がnupkgになっているので、nupkg解凍に対応したソフトウェアを使うか
拡張子を.zipに変えて試してみてください。
③chromedriver.exe
chromedriver.chromium.org
それぞれダウンロード先を埋め込みましたのでダウンロードしてください。
WebDriver,WebDriverSupportは解凍したフォルダ内の「lib\net**\」に.dllがあります。
・前提 - 環境構築
上記3点をダウンロードしてきたら、使いやすいように1か所にまとめましょう。
私は「Automation」というフォルダを作りました。
階層は下記の通りです。
AUTOMATION
│ automation.ps1
│
└─Data
│ chromedriver.exe
│
└─Selenium
│ .signature.p7s
│ Selenium.WebDriver.nuspec
│ [Content_Types].xml
│
├─images
│ icon.png
│
├─lib
│ ├─net45
│ │ WebDriver.dll
│ │
│ ├─net46
│ │ WebDriver.dll
│ │
│ ├─net47
│ │ WebDriver.dll
│ │
│ ├─net48
│ │ WebDriver.dll
│ │ WebDriver.Support.dll
│ │
│ ├─net5.0
│ │ WebDriver.dll
│ │
│ ├─netstandard2.0
│ │ WebDriver.dll
│ │
│ └─netstandard2.1
│ WebDriver.dll
│
├─package
│ └─services
│ └─metadata
│ └─core-properties
│ 8e4661a2052a4ed982f115e944130cde.psmdcp
│
└─_rels
.rels
今回使用するのはnet48なので、その中に「WebDriverSupport.dll」を入れてあります。
chromedriver.exeに関しては「Data」フォルダ直下に入れてます。
ここまで準備が整ったらあとはコーディングしていくだけです。
・コーディング
とりあえず私の書いたコードを載せます。
$dllPath = $null if($host.Name -match "ise"){ #ise上からでは$PSScriptRootが使えないため $dllPath = Split-Path $psISE.CurrentFile.FullPath -Parent } else { #ise以外で呼び出された際の処理 if($PSVersionTable.PSVersion.Major -ge 3){ #power shellのversionが3以上なら $dllPath = $PSScriptRoot } else { #3未満なら $dllPath = Split-Path $MyInvocation.MyCommand.Path -Parent } } $WebDriverPath ="$dllPath\Data\Selenium\lib\net48\WebDriver.dll" $WebDriverSupportPath = "$dllPath\Data\Selenium\lib\net48\WebDriver.Support.dll" $ChromeDriverPath = "$dllPath\Data\" Add-Type -Path $WebDriverPath Add-Type -Path $WebDriverSupportPath $driver = New-Object OpenQA.Selenium.Chrome.ChromeOptions $chromeDriver = New-Object OpenQA.Selenium.Chrome.ChromeDriver($ChromeDriverPath,$driver) $chromeDriver.Url = "https://www.google.co.jp" $box = $chromeDriver.FindElement([OpenQA.Selenium.By]::Name("q")) $box.SendKeys("powershell") $box.SendKeys([OpenQA.Selenium.Keys]::Enter) Start-Sleep -s 5 $chromeDriver.Quit()
それぞれ解説します。
$dllPath = $null if($host.Name -match "ise"){ #ise上からでは$PSScriptRootが使えないため $dllPath = Split-Path $psISE.CurrentFile.FullPath -Parent } else { #ise以外で呼び出された際の処理 if($PSVersionTable.PSVersion.Major -ge 3){ #power shellのversionが3以上なら $dllPath = $PSScriptRoot } else { #3未満なら $dllPath = Split-Path $MyInvocation.MyCommand.Path -Parent } }
これはPowershell ISE上で編集する際、現在のカレントディレクトリを取得する$PSScriptRootが使えないため、ISE上からプログラムを動作させた場合とそうでない場合でカレントディレクトリの取得方法を分けています。
$WebDriverPath ="$dllPath\Data\Selenium\lib\net48\WebDriver.dll" $WebDriverSupportPath = "$dllPath\Data\Selenium\lib\net48\WebDriver.Support.dll" $ChromeDriverPath = "$dllPath\Data\" Add-Type -Path $WebDriverPath Add-Type -Path $WebDriverSupportPath
ここでダウンロードしてきたdll、exeのパスを指定しています。
その後、Add-Typeでライブラリを呼んでいます。
$driver = New-Object OpenQA.Selenium.Chrome.ChromeOptions $chromeDriver = New-Object OpenQA.Selenium.Chrome.ChromeDriver($ChromeDriverPath,$driver)
ここでオブジェクトを作成しています。
$chromeDriver.Url = "https://www.google.co.jp"
$box = $chromeDriver.FindElement([OpenQA.Selenium.By]::Name("q")) $box.SendKeys("powershell") $box.SendKeys([OpenQA.Selenium.Keys]::Enter)
googleの検索ボックスに「powershell」と入力し、検索する処理です。
一番私が躓いた処理でもあります。
リファレンス通り
$box = $chromeDriver.FindElement(By.Name("q"))
と書いてもエラーが出て処理してくれません。
なのでSeleniumのFunctionですよと明示する必要があります。
それが([OpenQA.Selenium.By]::Nameなのです。
SendKeysで検索ボックスにテキストを送っています。
あとはEnterキーを押下する処理を送るだけです。
Start-Sleep -s 5 $chromeDriver.Quit()
結果を確認したかったのでとりあえず5秒待機させています。
その後、Quit()でWebDriverを閉じています。
SeleniumのリファレンスにはPowershellの例がないのでちゃんと動作させるのに3時間ぐらい費やしました...
Powershell、開発環境を入れられない端末でも使える言語なのですごい重宝するのですが、いかんせん凝ったプログラムはCやJava、Pythonで説明しているものが多いのでなかなか大変です。
次回はPowershellつながりで、UIAutomationについて書こうかと思います。