PowerShellにパスワード文字列を直接書かない手法

PowerShellで用意されているGet-Credential 関連を駆使して、PowerShellスクリプト内に直接パスワードを書かないで済む手法でスクリプトを作成した。

スクリプトを配置したディレクトリにscript.cred というファイルを作り、そこに暗号化されたパスワード文字列を配置する、という仕組み。

$authfile=$PSScriptRoot+".\script.cred"
$username="admin" #ユーザ名
$passwdstr="" # この変数にパスワード文字列が入る。
if((Test-Path $authfile) -eq $false){
    $creds = Get-Credential -UserName $username -Message $($username+"ユーザのパスワードを入力してください")
    $creds.Password | ConvertFrom-SecureString | Set-Content -Path $authfile | Out-Null
    $passwdstr=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($creds.Password))
}else{
    $passwdstr=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($(Get-Content $authfile | ConvertTo-SecureString)))
}

# この段階の $passwdstr には暗号化されていないパスワード文字列が入っている

なお、メモリ上の$passwdstr には暗号化されていない文字列が保存されているため、そこは注意が必要。

CommvaultのREST APIをPowerShellで使って見る

Commvaultバックアップの操作にREST APIを使うことができる。

・Commvault V11SP24の「REST API Overview
Commvault Rest APIの出力サンプル群

ログインして、指定したクライアントのサブクライアント情報を取得するPowerShellで作成してみた。

# CommCellユーザ情報
$restapiuser="admin"
$restapipasswd="パスワード"

# REST APIで使用するURL
# どちらを使うかについて https://documentation.commvault.com/11.24/expert/45592_available_web_services_for_rest_api.html
#$restapiurlbase="http://~:81/SearchSvc/CVWebService.svc/"
$restapiurlbase="http://~/webconsole/api/"

# 検索用文字列
$clientname="クライアント名"

# CommCell環境にログイン
$restapipasswdbase64=[Convert]::ToBase64String(([System.Text.Encoding]::Default).GetBytes($restapipasswd))
$headers=@{
    "Accept"="application/json"
    "Content-Type"="application/json"
}
$loginReq = @{
    username=$restapiuser
    password=$restapipasswdbase64
}
$loginresponse=Invoke-RestMethod -Method post -Uri $($restapiurlbase+"Login") -Body $($loginReq|ConvertTo-Json) -ContentType 'application/json' -Headers $headers

# 指定クライアントのサブクライアント情報取得
$headers=@{
    "Accept"="application/json"
    "Authtoken"=$loginresponse.token
}
$response=Invoke-RestMethod -Method Get -Uri $($restapiurlbase+"Subclient?clientName="+$clientname) -Headers $headers

# $response.subClientProperties.subClientEntity にサブクライアントの情報が入っているが
# 複数のサブクライアントがあると $response.subClientProperties.subClientEntity.subclientName が複数行になるので注意

# 表示
$response.subClientProperties.subClientEntity | ForEach-Object {
    $subcliententory=$_
    Write-host $subcliententory
 }

# CommCellログアウト
$headers=@{
    "Accept"="application/json"
    "Authtoken"=$loginresponse.token
}
$logoutresponse=Invoke-RestMethod -Method post -Uri $($restapiurlbase+"Logout") -Headers $headers

ただ、このスクリプトを使うとCommvault Event viewerに表示されるログイン情報が「Machine:unknown Locale:unknown」となる。

これを設定できるか調べて見たが、無いように思える。

vSphere 7.0 Update 1以降 vCLSという仮想マシンが勝手に起動してUPS連動シャットダウンに失敗する

vSphere 7.0 Update 1にアップデートして以降、UPS連動シャットダウンに失敗するようになった。

ログを確認していくと、仮想マシンがシャットダウンできない模様。

しかし、vCenter Serverから確認しても動いている仮想マシンは見えない。

そこでESXi のHost Clientに接続して確認すると、vCLSという作った覚えない仮想マシンが起動している。
そして、vCLSを手動で停止しても勝手に起動してくる。

確認してみると、vSphere 7.0 Update 1以降、vSphere DRS/vSphere HAなどのクラスタについて、起動するまでに時間がかかり重いvCenterサーバ仮想マシンではなく、クラスタの可用性のみを面倒見る仮想マシンとして vSphere Cluster Services を提供するようになったようである。

vSphere 7.0 Update 1 の vSphere Cluster Services (vCLS) (80472)
vSphere 7.0 documentation 「vSphere Cluster Services (vCLS)

これをUPS連動シャットダウンと組み合わせる場合の資料を探したところ、下記があった。

APC「PowerChute Network Shutdown v4.3/v4.4によるvSphere 7.0 Update 1でのvCLSの制御について
DELL「もう迷わない!HCI環境のUPS選定 シャットダウンについて

vSphereのクラスタをのvCLSをRetreatモードに変更することでvCLS仮想マシンが停止/削除することができる、というもの。

PowerShellでvCenterに接続して下記の様なスクリプトを実行して無効化を実行(APCのサンプルスクリプト disable_HA.ps1)

#!/usr/bin/pwsh

$server = "10.179.232.198" #"provide Vcenter server IP/hostname"
$username = "pcnsadmin" #"provide username to access vCenter"
$password = "APCapc@123" #"Provide Password to access vCenter server"
$cluster = "C" #"provide Name of the Cluster where Retreat mode needs to be enabled"

$env:HOME = '/root'
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
Connect-VIServer $server -Protocol https -User $username -password $password
$clid = (Get-Cluster $cluster).ID
Write-Host $clid
$myclid = $clid -replace 'ClusterComputeResource-',''
Write-Host $myclid
Get-AdvancedSetting -Entity $server -Name config.vcls.clusters.${myclid}.enabled | Set-AdvancedSetting -Value False -Confirm:$false

##Additional step for VSAN to turn off HA on the cluster
Get-Cluster -Name $cluster | Set-Cluster -HAEnabled:$false -Confirm:$false

Disconnect-VIServer -Force -Confirm:$false

PowerShellでvCenterに接続して下記の様なスクリプトを実行して有効化を実行(APCのサンプルスクリプト enable_HA.ps1)

#!/usr/bin/pwsh

$server = "10.179.232.198" #"provide Vcenter server IP/hostname"
$username = "pcnsadmin" #"provide username to access vCenter"
$password = "APCapc@123" #"Provide Password to access vCenter server"
$cluster = "C" #"provide Name of the Cluster where Retreat mode needs to be disabled"

$env:HOME = '/root'
Connect-VIServer $server -Protocol https -User $username -password $password
$clid = (Get-Cluster $cluster).ID
$myclid = $clid -replace 'ClusterComputeResource-',''
Write-Host $myclid
Get-AdvancedSetting -Entity $server -Name config.vcls.clusters.${myclid}.enabled | Set-AdvancedSetting -Value True  -Confirm:$false

##Additional step for VSAN to turn off HA on the cluster
Get-Cluster -Name $cluster | Set-Cluster -HAEnabled:$true -Confirm:$false

Disconnect-VIServer -Force -Confirm:$false

もう1つはAPCの資料および「PowerChute(TM) Network Shutdown v4.3 for Virtualization 補足説明書 日立編」には設定フローと共に掲載されている手法。

PowerChute Network Shutdownで「VM優先度付け」設定を有効にした上でvCLS仮想マシンを「優先度 高」で設定。vCenterサーバ仮想マシンを「優先度 中」、それ以外を優先度 低などに入れる。

「VMシャットダウン所要時間設定」と「VM起動所要時間設定」で「高」と「中」に対して0秒以上の値を設定

仮想化設定にある”仮想マシンと仮想装置、シャットダウンと起動”設定の「仮想マシンvApp 起動」にチェックを入れる

“ホストメンテナンスモード”の「タイムアウト」を「60秒」に設定

というもの。

綺麗に実行するのであればPowerShellを使った手法のほうが良さそうだ。

ESXiのみ環境でPowerCLIを使ってテンプレートもどきの動作をする手法

vCenterサーバがない、ESXiサーバのみ環境ではテンプレート機能が使用できない。

とはいえ、テンプレート化した仮想マシンの中身を見ると、ふつうと同じく仮想ハードディスクのvmdkファイルが存在しているので、vmdkファイルをコピーして、新規仮想マシンを作成するPowerCLIスクリプトをかけば、似たようなことができるな、と実験。

準備するもの

・PowerCLIをインストールした環境
Windows 10で実行したが、Linux環境にPowerShell+PowerCLIをセットアップしてもいけるはず。

・Windowsの場合、sysprepを実行してシャットダウンしたvmdkファイル
sysprepを実行して、次回起動時に初期セットアップが開始されるようにしたvmdkファイル。
VMware-toolsはインストール済みであることが望ましい。

・Linuxの場合、下記の情報などを削除
RHEL7の仮想化の導入および管理ガイド第4章 仮想マシンのクローン作成によれば
/etc/udev/rules.d/70-persistent-net.rules を削除
/etc/ssh/ssh_host_* を削除
/etc/sysconfig/network-scripts/ifcfg-eth* などから IPADDRESS,NETMASK,HWADDRなどの値削除

スクリプト本体

Import-Module VMware.VimAutomation.Core

#Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false

$vcenterserver="ESXiサーバ"
$vcenteruser="ユーザ名"
$vcenterpassword="パスワード"

$targetdatastore="仮想マシンをおくデータストア名"

# 仮想マシンの名前 / ランダムで作成して、あとから変更する手法
$vmnamebase="NewVM_"
$vmnametmp=Get-Random
$vmname=$vmnamebase+$vmnametmp

# 仮想マシンスペックと接続ネットワーク指定
$vcpu=2
$vmem=6
$network="VM Network"

# 指定できるGuestOSのIDを調べるには下記を実行すること
#   PowerCLIがサポートしている一覧 
#   [VMware.Vim.VirtualMachineGuestOsIdentifier].GetEnumValues()
# 代表的なもの
# windows8Server64Guest = Windows2012
# windows9Server64Guest = Windows2016
#$vmguestosid="windows9Server64Guest"

# 元ネタのWindowsが入ったvmdkファイルの指定
# 構築時にBIOS環境かEFI環境のどちらで作ったのか注意
$vmdkfilepath="[データストア名] windows2019/windows2019.vmdk"
$vmguestosid="windows9Server64Guest"
#$vmdkfilepath="[データストア名] win2016/win2016.vmdk"
#$vmguestosid="windows9Server64Guest"
#$vmdkfilepath="[データストア名] win2012/win2012.vmdk"
#$vmguestosid="windows8Server64Guest"
# 上記の元ネタが置いてあるデータストア名を下記でも指定する
$sourcedatastore ="データストア名"

# vCenterまたはESXiサーバに接続
Connect-VIServer -Server $vcenterserver -User $vcenteruser -Password $vcenterpassword -WarningAction 0
# パスワードを書きたくない場合は
# New-VICredentialStoreItem -Host $vcenterserver -User $vcenteruser -Password $vcenterpassword
# を実行すると、資格情報保存域に登録され、以降は下記だけで接続できるようになる
# Connect-VIServer -Server $vcenterserver

$virtualportgroup=Get-VirtualPortGroup -Name $network
$datastore=Get-Datastore -Name $targetdatastore
$vm=New-VM -Name $vmname -NumCpu $vcpu -MemoryGB $vmem -Portgroup $virtualportgroup -Datastore $datastore -DiskStorageFormat Thin -GuestID $vmguestosid -HardwareVersion vmx-14


$olddiskinfo=Get-Vm $vmname|Get-Harddisk # 一時的に作られたディスクの情報を保存
$sourcevmdk=Get-HardDisk -Datastore $sourcedatastore -DatastorePath $vmdkfilepath
# ディスクの削除
Remove-HardDisk -HardDisk $olddiskinfo -Confirm:$false


# コピー先データストアのパスを生成
$targetvmdktmp=$olddiskinfo.Filename
$ed=$targetvmdktmp.LastIndexOf("/")
$targetvmdk=$targetvmdktmp.Substring(0,$ed+1) # コピー先のパス

# OSが入ったvmdkのコピー
$adddiskinfo=Copy-HardDisk -Harddisk $sourcevmdk -DestinationPath $targetvmdk -DestinationStorageFormat Thin

#$vm = get-Vm $vmname
New-HardDisk -VM $vm -DiskPath $adddiskinfo.Filename

Get-ScsiController -VM $vm | Set-ScsiController -Type VirtualLsiLogicSAS

# 仮想マシン設定変更用オブジェクト定義
$newSpec = New-Object VMware.Vim.VirtualMachineConfigSpec

# USBコントローラの追加
# これがないとWindows環境でマウスが動かない
# https://communities.vmware.com/t5/VMware-PowerCLI-Discussions/Where-is-the-quot-Get-USBController-quot-cmdlet-Same-with-Remove/td-p/2155582
# 上記だとUSB 2.0コントローラ追加
$newSpec.deviceChange = New-Object VMware.Vim.VirtualDeviceConfigSpec[] (1)
$newSpec.deviceChange[0] = New-Object VMware.Vim.VirtualDeviceConfigSpec
$newSpec.deviceChange[0].operation = "add"
#$newSpec.deviceChange[0].device = New-Object VMware.Vim.VirtualUSBController # USB2.0コントローラ
$newSpec.deviceChange[0].device = New-Object VMware.Vim.VirtualUSBXHCIController # USB3.0コントローラ

# EFIかBIOSか
# https://docs.vmware.com/jp/VMware-Cloud-on-AWS/services/com.vmware.vsphere.vmc-aws-manage-vms.doc/GUID-898217D4-689D-4EB5-866C-888353FE241C.html
#https://github.com/vmware/PowerCLI-Example-Scripts/blob/master/Scripts/SecureBoot.ps1
# BIOSを設定する場合は、SecureBootをdisableにする必要がある
#$newSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
# 仮想マシン起動オプション変更用オブジェクト定義
$bootOptions = New-Object VMware.Vim.VirtualMachineBootOptions
#$newSpec.Firmware = [VMware.Vim.GuestOsDescriptorFirmwareType]::efi
#$bootOptions.EfiSecureBootEnabled = $true
$newSpec.Firmware = [VMware.Vim.GuestOsDescriptorFirmwareType]::bios
$bootOptions.EfiSecureBootEnabled = $false
$newSpec.BootOptions=$bootOptions


# 仮想マシンへの設定反映
#(get-view $vm).ReconfigVM_Task($newSpec)
$vm.ExtensionData.ReconfigVM($newSpec)


# 接続切断
Disconnect-VIServer -Server $vcenterserver -Confirm:$false

解説

仮想マシンにハードディスクを追加する

仮想マシンに新しく空っぽのハードディスクを追加する場合は、「New-HardDisk -VM 仮想マシン ~」で指定するが、Copy-HardDiskでコピーしてきたハードディスクを登録する場合が分かりづらかった。

結果としては、新規と同じく「New-HardDisk -VM 仮想マシン ~」でよかった。

今回の場合は、CopyHardDiskを実行した結果を変数にいれて、それをNew-HardDIskで登録、という形にした。

$adddiskinfo=Copy-HardDisk -Harddisk $sourcevmdk -DestinationPath $targetvmdk -DestinationStorageFormat Thin

$vm = get-Vm $vmname
New-HardDisk -VM $vm -DiskPath $adddiskinfo.Filename

仮想マシンハードウェアにUSB 3.0コントローラを追加する

Host Clientから作成した場合は、USB 3.0コントローラが作成されていたが、New-VMコマンドでは作成されなかった。このため、Windowsが起動した後にUSBマウスが存在できず、マウス操作ができなかった。(キーボードはPS/2キーボード扱いとして使えたようだった)

このUSB 3.0コントローラを追加するための操作がわからず、いろいろ調べたところ「Where is the “Get-USBController” cmdlet? (Same with Remove- and New-)」の記述を見て、USB 2.0コントローラの追加手法が分かった。

その後、VirtualUSBController を起点に調べることで、 xHCIはVirtualUSBXHCIControllerという名前であることがわかり、解決した

$vm=Get-VM -Name 仮想マシン名
$newSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$newSpec.deviceChange = New-Object VMware.Vim.VirtualDeviceConfigSpec[] (1)
$newSpec.deviceChange[0] = New-Object VMware.Vim.VirtualDeviceConfigSpec
$newSpec.deviceChange[0].operation = "add"
#$newSpec.deviceChange[0].device = New-Object VMware.Vim.VirtualUSBController # USB2.0コントローラ
$newSpec.deviceChange[0].device = New-Object VMware.Vim.VirtualUSBXHCIController # USB3.0コントローラ

# 仮想マシンへの設定反映
#(get-view $vm).ReconfigVM_Task($newSpec)
$vm.ExtensionData.ReconfigVM($newSpec)

設定を反映する部分は、ネタ元では「Get-View」を使っていたが、後述のSecureBootについての結果ではGet-VM経由で実施しているスクリプトがあったので、それを使用している。

BIOS/EFI切り替え

仮想マシンをBIOS起動にするか、EFI起動にするかを設定する手法。

2021現在のPowerCLIでNew-VMした時のデフォルトはEFI起動でセキュアブート有効になっている。

これをBIOS起動に設定する場合は、セキュアブートを無効にしてからBIOSに切り替える必要があるので下記にようになる。

$vm=Get-VM -Name 仮想マシン名
$newSpec = New-Object VMware.Vim.VirtualMachineConfigSpec

$bootOptions = New-Object VMware.Vim.VirtualMachineBootOptions
$bootOptions.EfiSecureBootEnabled = $false
$newSpec.BootOptions=$bootOptions

$newSpec.Firmware = [VMware.Vim.GuestOsDescriptorFirmwareType]::bios

$vm.ExtensionData.ReconfigVM($newSpec)



NetBackupのバックアップポリシー名を変更する

Veritas NetBackupでバックアップポリシー名を変更しようと思ったら、GUIにそういった操作が見当たらない。

調べるとコマンド bppolicynew コマンド の-renameto オプションを使うと変更できる、とのこと。

bppolicynew 元のポリシー名 -renameto 新しいポリシー名

という単純なものではあるのですが、複数をいっぺんに変更するとなるとポシリー名を間違わずに入力するのが非常に面倒となる。

バックアップポリシー一覧はbppllist コマンドで出力できるのだが、こいつには検索機能がなく、全部出力か、指定した1つ出力しかなく非常に使いにくい。

特定のバックアップタイプとか、特定のクライアントとかの検索をしやすいようにPowerShellを使って成形することとした。

「bppllist -allpolicies」は人には読みにくい形式ですが、出力される各行についてマニュアル内に解説があるのでそれに従って情報を取得することとした。

今回使用するのは

・CLASS行「フィールド1:ポリシー名」
・INFO行「フィールド1:ポリシータイプ」13=Windows,19=NDMPなど
・INFO行「フィールド11:ポリシーの有効無効」0=有効,1=無効
・INFO行「フィールド19:ポリシーを有効にする日付」(マニュアルは18となってるけど)
・INFO行「フィールド20:クラスID」ポリシー固有のID(マニュアルは19となってるけど)
・CLIENT行「フィールド1:クライアント名」

1つのポリシーに複数のクライアントが設定されている場合、CLIENT行は複数出力されるが、今回の環境ではそのような設定をしていないため考慮していない。

$tempfile = New-TemporaryFile
Start-Process -FilePath "C:\Program Files\Veritas\NetBackup\bin\admincmd\bppllist.exe" -ArgumentList "-allpolicies" -Wait -PassThru -NoNewWindow -RedirectStandardOutput $tempfile

$policyname=""
$policyname2=""

Get-Content $tempfile |ForEach-Object {
    $linetext=$_
    if($linetext.Contains("CLASS ")){
        $policyname2=$policyname
        $policyname=$linetext.Split(" ")[1]
        if($policyname2 -ne ""){
            #Write-Host $policyname2 " " $policytype " " $policyactive " " $policyclassid " " $policyactivedate
            Write-Host $policyclient "`t" "bppolicynew" $policyname2 "-renameto" $policyname2
        }
    }elseif($linetext.Contains("INFO ")){
        $policytype=$linetext.Split(" ")[1]
        $policyactive=$linetext.Split(" ")[11]
        $policyactivedate=$linetext.Split(" ")[19]
        $policyclassid=$linetext.Split(" ")[20]
    }elseif($linetext.Contains("CLIENT ")){
        $policyclient=$linetext.Split(" ")[1]
    }
}
$policyname2=$policyname
#Write-Host $policyname2 " " $policytype " " $policyactive " " $policyclassid " " $policyactivedate
Write-Host "bppolicynew" $policyname2 "-renameto" $policyname2

# New-tempfileを使用している場合は削除を忘れない
Remove-Item -Path $tempfile

なお、powershell上でDOSコマンドを実行するにあたり、ファイル出力をしないで直接PowerShellのパイプ連携を実施しようとしたのだが、実現できなかった。

このため、「-RedirectStandardOutput」で一時ファイルに出力してから処理という形を取っている。