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」で一時ファイルに出力してから処理という形を取っている。

Windowsのシリアルポートの番号確認バッチファイル


WindowsでUSBシリアルを繋いだ場合、そのCOM番号が何になったのかを確認するにはデバイスマネージャーの表示で確認する必要がある。

まぁ、いちいち面倒なので、ポートが認識されるとポップアップ表示をしてくれる「PortPop」とか、GUIで一覧を表示する「ViewComPorts」があったりする。

で、ViewComPortsを試してみようと思ったら、こちらはコンパイル済みバイナリが無い・・・ソースを見てみるとWMIから情報を引っ張っているっぽい。

ということはPowerShellで実装できるのでは?

と試してみたところ成功

バッチファイルの内容は下記3行。

@echo off
powershell -sta -ExecutionPolicy Unrestricted -Command "Get-WmiObject -Class Win32_PnPEntity -Filter \"PNPClass='Ports'\" | select Name,Manufacturer,DeviceID"
pause

なお、最初オンボードのIntel Active Managamentが「Service:Serial」だったので、それでfitlerしようとしたら、FDTIのは「Service:FTSER2K」だったので、両方に共通の「PNPClass:Ports」を選択しています。

FDTIのWMI出力サンプル

__GENUS                     : 2
__CLASS                     : Win32_PnPEntity
__SUPERCLASS                : CIM_LogicalDevice
__DYNASTY                   : CIM_ManagedSystemElement
__RELPATH                   : Win32_PnPEntity.DeviceID="FTDIBUS\\VID_0403+PID_6001+FTHG96ISA\\0000"
__PROPERTY_COUNT            : 26
__DERIVATION                : {CIM_LogicalDevice, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER                    : WIN10PC
__NAMESPACE                 : root\cimv2
__PATH                      : \\WIN10PC\root\cimv2:Win32_PnPEntity.DeviceID="FTDIBUS\\VID_0403+PID_6001+FTHG96ISA\\0000"
Availability                : 
Caption                     : USB Serial Port (COM4)
ClassGuid                   : {4d36e978-e325-11ce-bfc1-08002be10318}
CompatibleID                : 
ConfigManagerErrorCode      : 0
ConfigManagerUserConfig     : False
CreationClassName           : Win32_PnPEntity
Description                 : USB Serial Port
DeviceID                    : FTDIBUS\VID_0403+PID_6001+FTHG96ISA\0000
ErrorCleared                : 
ErrorDescription            : 
HardwareID                  : {FTDIBUS\COMPORT&VID_0403&PID_6001}
InstallDate                 : 
LastErrorCode               : 
Manufacturer                : FTDI
Name                        : USB Serial Port (COM4)
PNPClass                    : Ports
PNPDeviceID                 : FTDIBUS\VID_0403+PID_6001+FTHG96ISA\0000
PowerManagementCapabilities : 
PowerManagementSupported    : 
Present                     : True
Service                     : FTSER2K
Status                      : OK
StatusInfo                  : 
SystemCreationClassName     : Win32_ComputerSystem
SystemName                  : WIN10PC
PSComputerName              : WIN10PC

Intel Active ManagementのWMI出力サンプル

__GENUS                     : 2
__CLASS                     : Win32_PnPEntity
__SUPERCLASS                : CIM_LogicalDevice
__DYNASTY                   : CIM_ManagedSystemElement
__RELPATH                   : Win32_PnPEntity.DeviceID="PCI\\VEN_8086&DEV_A2BD&SUBSYS_82B4103C&REV_00\\3&11583659&1&B3"
__PROPERTY_COUNT            : 26
__DERIVATION                : {CIM_LogicalDevice, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER                    : WIN10PC
__NAMESPACE                 : root\cimv2
__PATH                      : \\WIN10PC\root\cimv2:Win32_PnPEntity.DeviceID="PCI\\VEN_8086&DEV_A2BD&SUBSYS_82B4103C&REV_00\\3&11583659&1&B3"
Availability                : 
Caption                     : Intel(R) Active Management Technology - SOL (COM3)
ClassGuid                   : {4d36e978-e325-11ce-bfc1-08002be10318}
CompatibleID                : {PCI\VEN_8086&DEV_A2BD&REV_00, PCI\VEN_8086&DEV_A2BD, PCI\VEN_8086&CC_070002, PCI\VEN_8086&CC_0700...}
ConfigManagerErrorCode      : 0
ConfigManagerUserConfig     : False
CreationClassName           : Win32_PnPEntity
Description                 : Intel(R) Active Management Technology - SOL
DeviceID                    : PCI\VEN_8086&DEV_A2BD&SUBSYS_82B4103C&REV_00\3&11583659&1&B3
ErrorCleared                : 
ErrorDescription            : 
HardwareID                  : {PCI\VEN_8086&DEV_A2BD&SUBSYS_82B4103C&REV_00, PCI\VEN_8086&DEV_A2BD&SUBSYS_82B4103C, PCI\VEN_8086&DEV_A2BD&CC_070002, PCI\VEN_8086&DEV_A2BD&CC_0700}
InstallDate                 : 
LastErrorCode               : 
Manufacturer                : Intel
Name                        : Intel(R) Active Management Technology - SOL (COM3)
PNPClass                    : Ports
PNPDeviceID                 : PCI\VEN_8086&DEV_A2BD&SUBSYS_82B4103C&REV_00\3&11583659&1&B3
PowerManagementCapabilities : 
PowerManagementSupported    : 
Present                     : True
Service                     : Serial
Status                      : OK
StatusInfo                  : 
SystemCreationClassName     : Win32_ComputerSystem
SystemName                  : WIN10PC
PSComputerName              : WIN10PC

NetAppのperfstatデータ収集ツールで取得したファイルを分解する


現用のNetAppの状況確認をするために「perfstatデータ収集ツール」というのが配布されている。

rsh/sshで対象のNetAppにログインして、いろんなコマンドを実行して、1つのテキストファイルとして出力をする。

この「1つのテキストファイル」というのがくせ者で、200MBぐらいのファイルができたりする。

これだとエディタで取り扱いづらいので細かく分割することにした。

中をみてみると「=-=-=-=-=-= CONFIG IPアドレス PRESTATS =-=-=-=-=-= aggr status -v」という感じで「 =-=-=-=-=-= 」区切りでファイルが出力されている。

それを元にファイルを分割したのが下記スクリプト。

$inputfile="x:\tmp\source\perfstat7_20191122_1400.txt"
$outdir="x:\tmp\output"

$filename=""

Get-Content $inputfile -Encoding UTF8 | ForEach-Object {
    $line=$_
    if( $line.Contains("=-=-=-=-=-=") ){
        $filename=$line
        $filename=$filename.Replace("=-=-=-=-=-= ","")
        $filename=$filename.Replace("/","-")
        $filename=$filename.Replace(":","")
        $filename=$outdir+"\"+$filename+".txt"
        New-Item $filename
    }
    if($filename -ne ""){
        $line | Add-Content $filename
    }
}

今回は1回しか実行しないので速度を気にする必要がないので簡単さを優先している。

もし、出力速度を気にするのであれば1行毎にAdd-Contentするのは非常に効率が悪いので工夫が必要となる。

参考例:「PowerShellで巨大なファイルをGet-Contentし、Export-Csvするのを省メモリで行う


2019/11/25 追記

上記のスクリプトだと遅すぎで、約200MBのファイル処理に5時間かかりました。

やはり改行のみ行が数万行ある、というのが悪かったようです。

また、同じヘッダ行が何回か登場するようで単純に「New-Item」としているとファイル名が重複しているというエラーがでてしまっていました。

さすがに5時間はないなーということで、高速化処理をしたのが下記となります。

実行にかかる時間は2分と大幅短縮となりました。

$inputfile="x:\tmp\source\perfstat7_20191122_1400.txt"
$outdir="x:\tmp\output2"

$filename=""

$results=@()
$linecount=0

Get-Content $inputfile -Encoding UTF8 | ForEach-Object {
    $line=$_
    if( $line.Contains("=-=-=-=-=-=") ){
        if($filename -ne ""){
            $results | Add-Content $filename
            $results=@()
            $linecount=0
        }
        $filename=$line
        $filename=$filename.Replace("=-=-=-=-=-= ","")
        $filename=$filename.Replace("/","-")
        $filename=$filename.Replace(":","")
        $filename=$outdir+"\"+$filename+".txt"
        if ( !(Test-Path $filename) ){
            New-Item $filename
        }
    }

    if($filename -ne ""){
        $results += $line
        $linecount++
        if(($linecount % 1000) -eq 0 ){
            $results | Add-Content $filename
            $results=@()
        }
    }
}

PowerShellでポートが空いているかチェック


PowerShellの「Test-NetConnection」使って指定したポートがアクセスできるかを確認することができる。

ただ、細かいあたりは指定できないようで、タイムアウト待ち時間やリトライ回数の指定などはできない。標準だと10秒待つようです。

雑に、192.168.1.1~192.168.1.254の範囲でssh用のポート22番が空いているものがあるかを確認するものとして以下を書いた。

for ($count=1; $count -lt 255; $count++){
    $ipaddr="192.168.1."+$count
    if( (Test-NetConnection $ipaddr -port 22).TcpTestSucceeded -eq "True"){
        Write-Host $ipaddr
    }
}

ただ、アクセスできなかった場合、下記の様に「警告」が出力される作りになっている。

警告: TCP connect to (192.168.1.10 : 22) failed
警告: Ping to 192.168.1.10 failed with status: TimedOut

探せば警告を抑止する手法もありそうですが、面倒なので省略しています。


Test-NetConnectionを使う限りはカスタマイズできないので「Test network ports faster with PowerShell」にていくつか例が提示されています。

その1

function testport ($hostname='yahoo.com',$port=80,$timeout=100) {
  $requestCallback = $state = $null
  $client = New-Object System.Net.Sockets.TcpClient
  $beginConnect = $client.BeginConnect($hostname,$port,$requestCallback,$state)
  Start-Sleep -milli $timeOut
  if ($client.Connected) { $open = $true } else { $open = $false }
  $client.Close()
  [pscustomobject]@{hostname=$hostname;port=$port;open=$open}
}

testport

hostname  port  open
--------  ----  ----
yahoo.com   80  True

その2

Param([string]$srv,$port=135,$timeout=3000,[switch]$verbose)

# Test-Port.ps1
# Does a TCP connection on specified port (135 by default)

$ErrorActionPreference = "SilentlyContinue"

# Create TCP Client
$tcpclient = new-Object system.Net.Sockets.TcpClient

# Tell TCP Client to connect to machine on Port
$iar = $tcpclient.BeginConnect($srv,$port,$null,$null)

# Set the wait time
$wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)

# Check to see if the connection is done
if(!$wait)
{
    # Close the connection and report timeout
    $tcpclient.Close()
    if($verbose){Write-Host "Connection Timeout"}
    Return $false
}
else
{
    # Close the connection and report the error if there is one
    $error.Clear()
    $tcpclient.EndConnect($iar) | out-Null
    if(!$?){if($verbose){write-host $error[0]};$failed = $true}
    $tcpclient.Close()
}

# Return $true if connection Establish else $False
if($failed){return $false}else{return $true}

Windows10とLinuxの両方で動くPowerShellスクリプトの考慮点


https://github.com/osakanataro/showroom-live のPowerShellスクリプトを作っている際に問題となった点のメモ書きです。

その1:改行コード

改行コードはWindows10ならCR+LF、LinuxならLFにする必要がある。それぞれ逆だとエラーになった。

コードを共有するのであれば、git for Windowsを使って管理し、Windowsに持ってくる時に改行コードを変換するようにした方が良い。

github上でreleaseとしてzipを提供する場合は、github側の機能でzipを作ると改行コードがLFになるので、Windowsユーザ向けに改行コードをCR+LFにしたバージョンを追加でアップロードした方が良い。

その2:文字エンコーディング

Windows10上のVisual Studio Codeで日本語文字列を含んで保存したところ、UTF-8 BOMなしで保存され、それをLinuxに持って行ったところ問題無く動作した。

しかし、UTF-8 BOMなしのファイルをWindows10上のPowerShell ISEで開くと文字化けする。

PowerShell ISEではUTF-8 BOMありを想定しているということで、BOMありに変えてみたところ、Linux上では「#!/usr/bin/pwsh」の#!より前にBOMのコードが挿入されてしまうため問題が発生した。

Windows10標準notepadなどでの編集には全く問題なく、PowerShell ISEのみの問題であるため、PowerShell ISEでの編集を諦め、UTF-8 BOMなしとした。

その3:ファイル名の取り扱い

まず、WindowsとUNIXでパス名の「\」と「/」の違い問題がある。

また、日本語文字列を使いファイル名を作成しようとした場合、許可される文字列がどの範囲かという判定が必要になったりする。

クロスプラットフォームで確実に動作させることを考えた場合は、いわゆるASCII文字だけでファイル名を構成した方が無難である。

その4:ParsedHtmlは使えない

Invoke-WebRequestで取得したWebページをParsedHtmlで解析して使用する、ということが出来るのは、Windows上でInternetExplorerコンポーネントを使える場合のみで、PowerShell Core環境では使えない。

このため、地道にパース処理を実装する必要がある。

その5:他のコマンド実行処理

PowerShellスクリプト内から、他のコマンドを実行するための処理として「Start-Process」というのがある。

ただ、実験してみた限りでは、Windows環境でDOSコマンドを実行する場合、Start-ProcessではPowerShellを実行した画面と同じ場所にDOSコマンドの出力内容をそのまま出力させることができなかった。(-Wait -PassThru -NoNewWindowをつけてもダメだった)

Windows環境の場合、PowerShell内から「cmd /c」を利用してDOSコマンドを実行した場合はPowerShellを実行した画面と同じ場所にDOSコマンドの出力内容を表示させることができた。

それに対して、Linux環境ではStart-Processを「-Wait -PassThru -NoNewWindow」オプション付きで実行することで通常のコマンドの出力内容をそのまま表示させることはできた。

その6:機種依存処理の条件分け

その3、その5のような機種依存処理をどのように実行するかについては「[Environment]::OSVersion.Platform」を利用した。

どのような文字列が取得できるかという点については「PlatformID Enum」にあるように「MacOSX」「Unix」「Win32NT」の3種類(他にもあるけど過去のモノなので無視)

MacOSXとUnixの処理はだいたい似たようにできるので、「Win32NT」の場合を検出した方がよさそうだったので、下記の様な処理を書いた。

    if([Environment]::OSVersion.Platform -eq "Win32NT"){
        cmd /c $streamlinkcmd $streamtypes $liveurl $quolity $streamlinkoption $streamlinkfilename
    }else{
        Start-Process -FilePath $streamlinkcmd -ArgumentList $streamtypes,$liveurl,$quolity,$streamlinkoption,$streamlinkfilename -Wait -PassThru -NoNewWindow
    }