最近の Windows はファイルのコピーがたいへんだ。
以下のバッチファイルはビルドしたファイルを C:\Program Files\xxxxx にコピーするだけの代物。だが…
C:\Program Files\xxxxxに書き込むには管理者権限が必要- PowerShell で管理者モードに昇格
- 管理者モードになるとネットーワークドライブが引き継がれない
- プロジェクトがネットワークドライブ(
net use Z: \\vmware-host\Share Folders\...上) にあるので外されると困るので、管理者モードになってからnet useコマンドを発行しなおす。
- プロジェクトがネットワークドライブ(
といったことをがんばってやっている。
@setlocal
@setn "prompt=[%~n0] "
set "TARGETNAME=TARGET.exe"
@set "TARGET=%~dp0.\Release\%TARGETNAME%"
@if not exist "%TARGET%" set "TARGET=%~dp0.\%TARGETNAME%"
set "TARGET=%TARGET%"
set "INSTALL=C:\Program Files\xxxxxxx"
@call :"%1"
@endlocal
@exit /b
:"_install"
copy /v "%TARGET%" "%INSTALL%"
pause
exit /b
:""
powershell "start-process -FilePath 'cmd.exe' -ArgumentList ('/s /c '+[char]34+((get-wmiobject win32_networkconnection | %%{ 'net use '+$_.LocalName+' '+[char]34+$_.RemoteName+[char]34 }) -join ' & ') + ' & ' + [char]34 + '%~dpnx0' + [char]34 + ' _install' + [char]34) -verb 'runas'"
exit /b
他の例として、使用中の nyagos.exe を置き換える時、そのままだと「他のプロセスが使用中です」となってしまう。そのため、
- コピーに失敗した時は、上書き先のファイルを別の名前にリネームして(他のプロセスが使用していてもリネームはできたりする)、それから改めて再コピー
といったことを PowerShell でやってたりする。
function Do-Copy($src,$dst){
Write-Verbose "$ copy '$src' '$dst'"
Copy-Item $src $dst
}
function Do-Rename($old,$new){
Write-Verbose "$ ren $old $new"
Rename-Item -path $old -newname $new
}
中略
try{
Do-Copy nyagos.exe $installDir
}catch{
$old = (Join-Path $installDir "nyagos.exe")
Write-Warning "Failed to update $old"
$now = (Get-Date -Format "yyyyMMddHHmmss")
$backup = ($old + "-" + $now)
try{
try{
Do-Rename $old $backup
}catch{
Write-Warning "Failed to rename $old to $backup"
Write-Warning "Try to kill nyagos.exe process"
taskkill /F /IM nyagos.exe
Do-Rename $old ($old + "-" + $now)
}
Do-Copy nyagos.exe $installDir
}catch{
Write-Error "Could not update installed nyagos.exe"
Write-Error "Some processes holds nyagos.exe now"
}
}
こういうのを今までずっとバッチファイルとか PowerShell でやってきたんだけど、いい加減たいへんだ。 あんまり汎用化していないので、Visual Studio のプロジェクトごとにいちいちコピーして修正していたりするし、多くの処理を1行PowerShel の中に詰め込んだりしている箇所もあって、保守がしづらい。
そこで「コピー先がどこであろうと、がんばってコピーするプログラム」を Go 言語で作った。 その名も fcopy 、不屈のコピーだ。
文法的には cp とだいたい同じ。
$ fcopy ソースファイル名1 ソースファイル名2 ... 宛先フォルダー OR $ fcopy ソースファイル名 コピー先ファイル名
このコピーは、上書きするファイル名が使用中でも、管理者権限が必要なフォルダーでも、うまいことアレしてくれる (上書きできないファイルをリネームしたり、UACダイアログを出したりする)。
こういう OS の API を直接呼びたいし、その上ロジックがいろいろ面倒で C言語ではしんどい…といった用途にはGo言語は本当に向いている。
これ一つでほとんどのケースはカバーできるはずだ。
(あと、本当は「特権昇格できない一般ユーザーは、管理者アカウントにスイッチして昇格する」とケースもある。自分はそういうシチュエーションになったことはないのだが、いずれは対応せねばなるまい)