Powershell」カテゴリーアーカイブ

Powershellによるファイルのリネーム

画像や音楽データを整理していると、一括でファイル名を変更したくなる場面があります。

私の場合はかつて採用していたファイル命名ルールに問題があることが分かり、新しく決めたファイル命名ルールに更新したくなるというケースが多いです。

リネーム用ツールなども色々あるとは思うのですが、柔軟に運用できるという点でこういう時はPowershellの出番です。

Powershellによるファイルのパラメータ取得

Powershellでリネームを行う場合は”Get-Childitem”コマンドレットを使うのが一般的ではないかと思います。このコマンドレットはフォルダ内のファイルのパラメータを一括取得してくれます。私はもっぱらファイルやフォルダの処理に使いますが、レジストリハイブや証明書ストアにも使えるそうです。

今回はC:\Photoに入っているyyyymmdd.jpgという命名ルールで名前のついている画像データをyymmdd.jpgに変更するという場面を想定します。

まず、フォルダ内のjpgファイルの情報を取得するには以下の通りの記述でOKです。

[Powershell]
Get-Childitem "C:\Photo" *.jpg

拡張子を指定するとその拡張子に当てはまるファイルだけのパラメータを取得できます。

Powershellによるパイプライン接続

次にリネーム処理を行うためのパイプライン接続を行います。パイプラインというのはちょっと難しそうですが、連続した処理を行う際に1つ目の処理と2つ目の処理を接続する役割を果たします。

例えば、 フォルダ内のファイル名を取得する→特定のファイル名でフィルタする→結果をcsv出力する といった処理があった場合、矢印部分にパイプライン処理が使われます。記述は簡単で、専用の演算子”|”を入れるだけです。

Powershellによるリネーム

リネームを行います。取得されたファイル名のデータはパイプライン経由で使用できます。パイプライン経由のデータは自動変数”$_”に格納されているので、この変数に対して処理を行えば大丈夫です。

ファイルのリネームの場合は”Rename-Item”コマンドレットが使えます。コードはこんな感じになります。

[Powershell]
Get-Childitem "C:\Photo" *.jpg | Rename-Item -NewName {$_.Name -replace "20", ""}

自動変数のNameパラメータに対して”20″を””(空)に置き換える処理を行っています。

続きを読む

PowershellによるCSVファイルからのデータ抽出

CSV形式のデータから特定の文字列を含む行だけ取り出す必要が生じた場合、通常はExcelで読み込んでオートフィルタで抽出するのがお手軽だと思いますが、

  1. データ量がExcelで取り扱うには多い
  2. 抽出条件が複雑
  3. 作業を行うPCにExcelがない

などといった理由からPowershellを使ってデータ抽出をすることがあります。

例えば下のようなCSVデータがあったと仮定して、ここからデータ抽出を行います。

PowershellにはCSVのインポート/エクスポートを行うコマンドレットがあるので、入出力はこれでOKです。

データ抽出にあたってはインポートした内容を変数に格納し、Where-Objectコマンドレットで抽出します。これは正規表現が使えるので、複雑な抽出条件も設定できます。

下のスクリプトはCSVから駅名に”信濃”を含む行を取り出して、CSVと同じフォルダに”result.csv”として出力するものです。

#Powershell
#CSVがあるディレクトリに移動
Set-Location 'C:Users\hoge\csv'

#CSV読み込み
$csv = Import-Csv .\nagaden.csv -Encoding Default

#データを抽出("信濃"を含む駅名のみ抽出)後、CSV出力
$csv | Where-Object {$_.駅名 -match '.*信濃.*'} | Export-Csv ./result.csv -Encoding Default -NoTypeInformation

結果はこうなりました。

Powershellの地味にありがたいところはWindows10には絶対入っているということです。突然借りたPCでも基本的には使えるので、突発的にアドリブで何かしなくてはならない時に助かっています。

Powershellのエスケープシーケンス

Powershellで文字列を整形するときに、タブの取り扱いに苦労していました。
例えばタブが含まれる文字列からタブ部分を置換して除去できると良いのですが、その方法が良く分かっていませんでした。

Powershellは以下のドキュメントにある通りの特殊文字シーケンス(いわゆるエスケープシーケンス)を認識してくれるため、これを使えば特殊文字を含む文字列操作が可能だそうです。

特殊文字について

[Microsoft Docs]

改行コード(`n)とキャリッジリターン(`r)もあるので、LFとCRLF間での改行コード変換なんかもこれで大丈夫そうです。

UWPアプリをPowershellでアンインストールする

Windows10にはストアでダウンロードできるUWPアプリ(ストアアプリ・モダンアプリとも)がありますが、Windowsに付属しているアプリは”アプリと機能”でアンインストールすることができません。

Powershellを使うとアンインストールすることができます。限界まで不要なアプリをそぎ落としたい時や、付属のUWPアプリの再インストール時に便利です。

アプリの名前を調べる

アプリのアンインストールにあたって、最初にPowershellで指定するアプリの名前を特定する必要があります。

まず、PC内のUWPアプリ一覧を以下のPowershellスクリプトで表示します。

[Powershell]
Get-AppxPackage | Select-Object -Property Name

アプリケーション名が列挙されるので、それらしい物を探します。今回は”フォト”を例に使ってみようと思います。

名前からして”Microsoft.Windows.Photos”で間違いなさそうですが、本当にこの名前がフォトを指しているのかどうかを念のため確認します。

アプリの名前を確認する

以下のPowershellスクリプトを実行します。

[Powershell]
$PackageFamilyName = (Get-AppxPackage Microsoft.Windows.Photos*).PackageFamilyName

$AppID = (Get-AppxPackage -Name *Microsoft.Windows.Photos* | Get-AppxPackageManifest).Package.Applications.Application.Id[0]

$Run = "Start-Process shell:AppsFolder\" + $PackageFamilyName + "!" + $AppID
Invoke-Expression $Run

スクリプト中の”Microsoft.Windows.Photos”の部分は調査したいアプリに応じて読み替えてください。

このスクリプトを実行して、アンインストールしたいUWPアプリが起動すればOKです。アプリの名前についてはネット上にも色々情報があるので、それらを参考にするのも良いと思います。

アプリのアンインストール

先ほど確認したアプリ名を使って、以下のスクリプトを実行します。

[Powershell]
Remove-AppxPackage -Package "Microsoft.Windows.Photos"

これでアンインストールが行われます。

アプリの再インストール

UWPアプリの再インストールはMicrosoftストアから再度インストールすればOKです。

参考情報

UWP アプリを PowerShell から起動する

[MURA’s HomePage 様]

こちらの情報を参考にさせていただきました。ありがとうございました。

自己昇格するバッチファイルを作成する

PCにあまり詳しくない人にバッチファイルを実行してもらう時に困るのが、”管理者実行”の問題です。コマンドの内容によっては管理者権限で実行しないといけないものもあるのですが、事前に説明しておいても右クリックして管理者実行してもらえないというケースがよくあります。

ネットで調べてみると、バッチファイルの書き方次第ではバッチファイル自身が権限を昇格してコマンドを実行できるような書き方ができるようなので、試してみました。

管理者権限の判定

今バッチファイルが管理者権限・ユーザ権限どちらで実行されているかを判断するには、openfilesコマンドを使います。

このコマンドは現在開かれている共有フォルダや共有ファイルを列挙するコマンドなのですが、管理者権限がないと実行できません。

ユーザ権限で実行した場合は変数errorlevelに1、管理者権限で実行した場合は0が格納されるので、これを権限の判定に使用します。

自己昇格

自分自身の権限を昇格して実行するには、powershellの力を借ります。powershellでよく使う”start-process hogehoge -verb runas”という管理者でプロセス実行する文がありますが、ここで実行するプロセスにコマンドプロンプトの変数である”%~0”を使用します。

%~0はバッチファイル自身を意味する変数で、これにより自分自身を再度管理者権限で実行することができます。

表示上の一工夫

最初に権限確認のため実行するopenfilesですが、実行結果が1枚目のコマンドプロンプトに表示されてしまいます。ちょっとうっとうしいのでこれを表示させないために、結果を標準ログにまとめて捨ててしまいます。

記述としては”openfiles > NUL 2>&1”となり、”2>&1”の部分が「エラーログを標準ログにまとめる」の意で、”>NUL”の部分が「出力を捨てる」という意味合いになります。

なので、口語に直すと「openfilesの出力を捨てる。ただし、エラーログと標準ログはまとめる」といった感じになります。ちなみに”>NUL”と”2>&1”の順序を逆にすると普通に結果が画面に出力されてきますのでご注意下さい。

できあがったバッチファイル

[bat]
@echo off

rem openfilesを実行して実行権限を判定できる数値をerrorlevelに格納する
rem このopenfilesの結果はウインドウに出力しない
openfiles >NUL 2>&1 

rem errorlevelの値が1(ユーザ権限)だった場合は管理者権限により自身を再実行
if %errorlevel% equ 1 (
	powershell start-process %~0 -verb runas
) else (
	rem 実行したい処理(例としてopenfiles)
	openfiles
)

参考情報

こちらのサイトの情報が大変参考になりました。ありがとうございました。

How to check in a batch file if you are running it elevated

[winaero.com]

Powershellの正規表現を使ったcsvの整形

ある表データの利用が必要になったのですが、各項目が”[カラム名]=[値]”で表記されており、スペースで区切られているという変わった書式になっていました。

これだけならまだしも、値のないカラムはカラム名ごと省略されており、カラム名とスペースをカンマで置き換えても値がずれるという面倒な書式でした。

ちょうどこんな感じです。

<Powershell>
$str = @(
    "pref=saitama city=saitama ward=urawa machi=kitaurawa",
    "pref=saitama city=kumagaya machi=honcho
"
)

2行目の方にも”ward=”という空の表記があれば列が揃うのですが、このままだとwardの値とmachiの値が混ざってしまいます。

Powershellは正規表現が使えるとのことだったので、勉強がてら変換を試してみることにしました。できあがりはこんな感じです。

<Powershell>
#加工する文字列
$str = @(
    "pref=saitama city=saitama ward=urawa machi=kitaurawa",
    "pref=saitama city=kumagaya machi=honcho"
)

#city=[何とか]の次がmachi=[何とか]だった場合ward=を挿入
$str = $str -replace '(city=[a-z]+) machi=','$1 ward= machi='

#[カラム名]=を削除
$str = $str -replace '[a-z]+=',''

#スペースをカンマに変換
$str = $str -replace '\s',','

#ヘッダ変数
$header = "pref,city,ward,machi"

#ヘッダを配列の先頭に挿入
$str = @($header) + $str

#文字列をcsv出力
$str | Out-File "C:\hoge\hoge.csv" -Encoding UTF8

今回のケースでは一致したcityの値を置き換え後の文字列でも使用したかったのですが、これは正規表現のグループ化という機能で実現可能です。

正規表現の条件指定部分を()でくくっておくと、置き換え後の文字列で$1という名前で参照できます。上の例では配列の2つ目の値でcity=kumagayaという文字列を検索条件に使いつつ、置き換え後文字列で改めて呼び出しています。

出力されたcsvも表計算ソフトでスムーズに読み込めるのでこれで良いかなと思います。

RobocopyとPowershellを利用した世代管理バックアップ

バックアップをする時にバックアップ時点毎にデータを保持しておく”世代管理”という方法があります。ちょうど世代管理でバックアップを行いたいデータがあったので、いつも使っているRobocopyとPowershellを組み合わせてバックアップ用のスクリプトを作りました。

#Powershell
#コピー元コピー先の設定
$CopyFrom = [コピー元]
$CopyDest = [コピー先]

#コピー先のフォルダ数確認
#6フォルダ以上ある場合は古いものから削除
$FolderCount = (Get-ChildItem $CopyDest | Where-Object { $_.PsIsContainer }).Count

While($FolderCount -ge 6){
    #コピー先内のフォルダ一覧を昇順ソートして変数に格納
    $DltPass = Get-ChildItem $CopyDest | Where-Object { $_.PsIsContainer } | Sort-Object
    
    #最も古い日付のフォルダ削除
    Remove-Item -Path ($CopyDest +"\"+ $DltPass[0].Name) -Force

    $FolderCount = (Get-ChildItem $CopyDest | Where-Object { $_.PsIsContainer }).Count
}

#当日の年月日からコピー先フォルダを生成
New-Item ($CopyDest +"\"+ (Get-Date -Format "yyMMdd")) -ItemType Directory

#データコピー
Robocopy $CopyFrom ($CopyDest +"\"+ (Get-Date -Format "yyMMdd")) /S /R:5 /W:0 /LOG+:$CopyDest\log.txt

実行するとコピー先フォルダの下にyymmdd形式で命名されたフォルダができ、その下にデータがコピーされます。コピー先フォルダの下に6つ以上フォルダがある場合、フォルダ名で昇順ソートして先頭に来たフォルダを削除する仕組みです。

念のため、コピー先フォルダ直下にバックアップのログが残るようにしています。エラー処理等は一切していませんが、自分用ならとりあえずはこれで十分といったところです。

Robocopyは単体でも便利なのですが、Powershellから呼び出して使うこともできるので、アイデア次第では様々なやりたい事に対応できて便利です。

バッチファイルからPowershellのps1ファイルを実行する

以前Powershellのps1ファイルをダブルクリックで実行する方法についてエントリを書きました。

今回も似たような内容なのですが、今回は”Powershellを日常的に実行しない方にダブルクリックで実行してもらう”場合の話です。

相手がPCにあまり詳しくない方だった場合などにスクリプトが書かれたファイルを渡して、「とりあえずこのファイルを実行してもらえれば○○ができるようになるから」という運用ができると便利です。

ところが、Powershellスクリプトの実行はWindowsのデフォルトで制限されているので、実行してもらう前にPowershellの実行ポリシー(Executionpolicy)を緩める必要があります。

この実行ポリシー変更も含めてダブルクリックで行う際に、バッチファイルからps1ファイルを実行するようにするとうまく行くそうです。

コード

【bat】
@echo off
powershell -NoProfile -ExecutionPolicy RemoteSigned .\hoge.ps1

上記のバッチファイルを作成し、実行してもらいたいPowershellスクリプト(hoge.ps1)と同じフォルダに配置します。あとはバッチファイル側を実行してもらえれば連動してPowershellスクリプトが動くという寸法です。

ファイルが2つになってしまうのが少々難ですが、運用はやりやすいのではないかと思います。

パラメータについて

Powershellの実行コマンドのパラメータとして-NoProfileと-ExecutionPolicyという2つがありますが、まず-NoProfileはPowershell実行時に読み込む環境情報を読み込まないという設定です。今回想定している利用場面では無くても大丈夫そうなパラメータですが、念のための記述という感じです。

-ExecutionPolicyは実行ポリシーを設定するためのパラメータです。これを設定しているのがバッチファイルからの起動のポイントになります。

緩めた実行ポリシーはどうなるのか

必要に迫られているとは言え、今回はデフォルトからPowershellの実行ポリシーを緩和しています。これはスクリプト実行後どうなるのでしょうか。

今回のようにpowershell.exeの起動オプションとしてExecutionPolicyを指定した場合、設定した実行ポリシーは当該プロセス実行時しか有効にならないようです。つまり、スクリプト実行後設定が元に戻ります。特に必要ないのであればデフォルトの実行ポリシーにしておいた方がいいと思うので、この点安心です。

参考情報

以下の情報を参考にさせていただきました。ありがとうございました。

バッチファイルから PowerShell を呼び出す方法

PowerShellのExecutionPolicyのスコープとかについて詳しく

[Qiita]

About PowerShell.exe

[docs.microsoft.com]

Powershellで列が複数あるCSVを読み込む

以前ファイルの整理のためにファイル一覧をCSVに書き出したり、逆にImport-Csvコマンドレットで読み込む方法を調べました。

このImport-Csvコマンドレットですが、複数列あるCSVを読み込むとどうなるか気になったので、試してみることにしました。

試しに以下のようなカンマ区切りのCSVを作成します。

ID,名前
1,りんご
2,ぶどう
3,あんず

これをImport-Csvで読み込むと、以下のように読み取ってくれます。1行目がヘッダとして自動認識されます。

ID 名前
— —-
1 りんご
2 ぶどう
3 あんず
4 なし

この中から特定の値を取り出したい場合、変数に格納した上で処理ができます。

仮にC:\hoge内に”table.csv”があったと仮定すると、

cd C:\hoge

#CSVを読み込んで変数に格納
$csv Import-Csv table.csv

#添字”2”の"名前"の値を取り出す
$csv[2].名前

CSVを格納した変数には配列の要領でアクセスが可能です。例では添字を2にして名前の値を取り出しています。これを実行すると結果は”あんず”と出てきます。2なら”ぶどう”ではないのかと思うのですが、Powershellの配列の添字は0から始まっているので、2は”3番目の値”を示すことになります。

まだ触りだしたばかりでCSVと配列についてはこの程度しか理解できていないのですが、これも結構使い出がありそうに思えます。

Powershellでフォルダ名称をCSVに並べたり、CSVからフォルダを生成したりする

表題の通り、時々フォルダ内のフォルダ名をリスト化したくなったり、逆にリスト化したフォルダ名称を元にフォルダを自動作成したくなることがあります。

困った時のPowershellで実行可能です。なお、リストはCSVを前提としています。CSVだとExcelで読み書きもできるので、その点で便利です。

フォルダ名称をCSVにリスト化する

Get-ChildItem C:\hoge | Where-Object {$_.Attributes -eq "Directory"} | Select-Object PSChildName | Export-Csv foldername.csv -Encoding UTF8

上記のサンプルではC:\hoge直下のフォルダを一覧にしたCSV、forlername.csvを作成します。Where-Objectの部分でAttributesを使ってフォルダのみを指定しています。

次のSelect-Objectは、デフォルトのままだとフォルダの作成日やパスなどもCSVに出力してしまうので、フォルダ名称のみのリストにするために、値をPSChildNameに限定しています。

CSVからフォルダを作成する

仮に下記のようなデータが格納されたCSV、folder.csvがあったとします。先頭行のfoldernameはヘッダです。

foldername
hoge
piyo
fuga

続いて以下のコードを実行します。

Import-Csv folder.csv | ForEach-Object {New-Item $_.foldername -ItemType Directory}

これでカレントディレクトリにhoge piyo fugaの3つのフォルダが生成されます。

この2つのスクリプトを組み合わせると、フォルダ構造の把握の他にもファイルを除いたコピペのような操作もできるので重宝しています。必要であれば再帰実行用の-Recurseオプションをつけて頂くと便利なのではないかと思います。