ネットワークプリンタが削除できない場合の対処

この記事の要約

バイスとプリンタで消したはずのプリンタがまだ存在するというエラーが出て設定変更ができないときのための対処法です。
「ネットワークプリンタ 削除できない」などでググってもレジストリを消してスプーラサービスを再起動しろ、という方法だけしか出てこないのでPowerShellでWMIを操作したら問題を解決できたという内容です。CMDでの操作法も一応書いておきます。

続きを読む

PowerShell2.0で連想配列をPSCustomObjectに変換する

PowerShell2.0では連想配列[PSCustomObject]をつけて直接オブジェクトに変換することができません。
なので出来るようにする高度な関数を車輪の再発明します。
PSv3以降が使える環境ならまるで意味が無いものですので読むだけ時間の無駄です。

続きを読む

続PowerShell 2.0つらみばなし

PowerShellアドベントカレンダーで書くつもりでいたら時間なくて急いで書いたせいで漏れた小ネタを書いておく。

対象のパスにアイテムがあるか調べるために以下のように書くことがある。

(Get-ChildItem -Path $path).Count -eq 0

子アイテムが無い場合カウントは0なのでtrueがかえることを期待するが実際はfalseが返る。

(Get-ChildItem -Path $path).Count -ne 0

こちらはtrueが返る。

アイテムが0であることを期待して上のようにすると常にfalseが返るようになる。下段はアイテムが0でも常にtrueが返る。 アイテムが1個の場合でもCountが何も返さないので同じ。

理由はアイテムが1つだと返されるのがFileInfoで2個以上だとObject[]だから。v3以上のように変数にCountがあるわけではなく単に配列のCountを読んでいるだけなのでアイテムの数が0または1では判定に失敗する。

(Get-ChildItem -Path $path|Measure-Object|Select-Object -ExpandProperty Count) -eq 0

少なくともCountは失敗しない

いまどきPowerShell2.0でどうにかするしかないつらみ話

PowerShellアドベントカレンダー2015 14日目です。 PowerShell2.0でどうにかするしかないのでどうにかしている話です。

はじめに

こんにちは、PowerShellとDSCでWindowsHyper-Vの運用自動化したい!と転職の面接で熱く訴えたところ 「ちょうど今やってる現場があるんだけど」と大喜びで受けたらPowerShell2.0でVMWareで手動構築だったgidodongasです。 VMWareでもPowerShellは使うのでXenとかじゃなかっただけマシかと思うことにしています。 *1

私の職場ではWindows構築案件は未だに2008R2しかありません。DSC?ありません。PS2にそんなもんありません。 自動化の概念もないに等しいのでWMFの導入もありません。 セキュリティ対策からインターネットに出られないので業務にあると便利というか必須に近いソフトウェアも手にはいりません。導入申請しても許可が降りません。

そんなわけで必要なあらゆることを業務PCのWindows7に標準で入っているPowerShell2.0でやる破目になったのです。この時点でだいぶつらみがありますがPS4とVS2013+PowerShellToolsに慣れきってすっかり忘れてたPS2のつらみがどんどんでてくる…

PowerShell 2.0がつらい理由

細かく挙げてたらきりがなさすぎたのでざっくりいうとコレです

  1. コマンドレットが質・量ともに足りてない
  2. [PSCustomObject]@{}がない
  3. ISE産業廃棄物問題
  4. そもそも今時PS2かよという問題

1はまあ諦めるしかありません。シェルだけあってOS依存のコマンドレットも多いです。 ただGet-Contentに-rawが無いのとImport/Export-CSVに-encoding defaultが無いのが致命的です。 シェルのデータ入出力はテキストベースになる事が多い*2のでこれらが無いとデータの入出力で余計な苦労をします。 コマンドレットが無いなら自分で書きましょう、我々には.netという強力な武器があります。

2はデータ加工してると延々Add-Memberするはめになってすごく辛い [PSCustomObject]@{hoge=1;huga=2}したい。というかAdd-Member -NotePropertyMembersも無い

3は私がPS2が辛いと感じる最大の原因 無いなら自分でなんとかしたやらあ!とコード書くとどんどん挫けそうになるのはコイツがゴミゴミウンコだから。インテリセンスが仕事しないので.netクラス名全部手打ちするハメになる。大抵綴り間違いでエラー吐かれる。 .netのインストールフォルダからdll名さらってきてBaseNameでリスト作っておくぐらいしかできない。

4はそもそも今時PS2でほっとかれるような環境がまともなわけがないというつらみの根源的原因。

とにかくどうにかする

前置きが長くなったのでどんどん行きましょう。

.Net4を有効にする

デフォルトでは.net2.0しか使ってくれないので4を有効化します。 WPFを使ってGUI付きツールを作る場合3.5.1以上必須ですのでぜひやっておきましょう。

#.net4.0を有効にする。
$xml = @"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0.30319" />
    </startup>
</configuration>
"@

$ps86 = 'C:\Windows\SysWOW64\WindowsPowerShell\v1.0'
$ps64 = 'C:\Windows\System32\WindowsPowerShell\v1.0'
$xml|Out-File $ps86\powershell_ise.exe.config 
$xml|Out-File $ps86\powershell.exe.config
$xml|Out-File $ps64\powershell_ise.exe.config 
$xml|Out-File $ps64\powershell.exe.config
Get-Contetに-rawオプションがない

なので車輪を再発明します。

function Get-ContentRaw
{
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        # path
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $Path,

        # エンコーディング。デフォルトはSJIS。.netで指定できる文字コード入力
        [string]
        $Encoding = 'SJIS'
    )

    Begin
    {
        $enc = [System.Text.encoding]::GetEncoding("$Encoding");

    }
    Process
    {
        $reader = New-Object -TypeName System.IO.StreamReader -ArgumentList "$Path",$enc;
        $text   = $reader.ReadToEnd();
        $reader.Close();
        return $text;
    }
    End
    {
        $reader.Dispose();
    }
}
Import-CSVに-encoding defaultがない。
Get-ContentRaw -Path $Path|Convertfrom-Csv

または

Get-Content -Path $Path -Encoding String|Convertfrom-Csv

Excelから出力されたCSVは糞率が高いので改行記号の置換*3やインポート前にヘッダの編集*4が必要になることがあります。

System.Collections.ArrayListを使う時の注意

ArrayListを使う使わないで速度で雲泥の差が出ますがPS2のExport-CSVはアホの子なので直接パイプすると正しく展開してくれません。%{$_}を挟んで一度展開してから入力させてやるとうまく入ります。というか最初からpscustomobjectでNew-Objectしてください。

$arrayList = New-Object System.Collections.ArrayList;
$arrayList.add($obj);

#$objがpscustomobjectの場合
$arrayList|Export-Csv -Path $path;
#$objがpsobjectの場合
$arrayList|%{$_}|Export-Csv -Path $path;
WPFをつかう

PS2しか使えないということはネット上のソフトウェア類の導入も制限されるような環境でる可能性が高いといえます。 UIのあるツール類を作る必要に迫られることもあるでしょう。 XAMLは大文字小文字の区別があり間違えるとエラーになります。注意してください。 XAMLは人間が手打ちするようなものではありません、頑張ってください。

Add-Type -AssemblyName PresentationFramework;
Add-Type -AssemblyName System.Management;

#ps2のコンソールはMTAで起動する。STAでないとWPFは死ぬのでSTAで再起動する。
$MTA = [System.Threading.Thread]::CurrentThread.ApartmentState.ToString() -eq 'MTA';
if($MTA){
    $thisScript = "'"+$MyInvocation.MyCommand.Path+"'";
    $psExePath  = "${PSHOME}\powershell.exe";
    Start-Process $psExePath -ArgumentList "-STA -WindowStyle hidden &$thisScript";
    Exit;
}
#UIを定義するXAMLを読み込む
[xml]$xaml = Get-Content -Path $xamlPath ;
$reader = New-Object System.Xml.XmlNodeReader $xaml;
$window = [Windows.Markup.XamlReader]::Load($reader);
#UIの定義
$button = $window.findname('button')
$button.add_Click(
#動作を書く
)

#windowを表示する
$window.ShowDialog()|Out-Null;

おわり

VBなどのライブラリ使ったスクリーンショット撮影ツールExcel写真帳、ZIPの展開、ネットワークアダプタの設定などもうちょっとPowerShell2.0でいろんなことをどうにかする方法を書きたかったんですが、データ持ち出し禁止なので自宅で打ちなおしていたら障害対応で時間切れになってしまいました(´・ω・`)スミマセン。あとでちゃんと書いておきたいと思います。

PowerShellv5が登場してどんどん新技術が登場しているのになんで自分はこんな苦労せにゃならんのだ!と考える日々ですが私と同じような境遇の人の業務改善の一助になれば幸いです。

12/15追記:WPFでMTAの判定のトコでmatchが抜けてました

*1:最近面接したその部長がそもそもハイパーバイザーの事をよくわかっていなかった事が判明、仮想化が業務なのに

*2:具体的に言うとExcelから出力された塵CSV

*3:セル内改行

*4:PS2CSVコマンドレットはヘッダが空のときH1,H2などで補完してくれずエラーになる

AcadRemoconをPowerShellで使う

IronRubyなどで実行している例があったのでやってみたら案外普通に使えて便利 COMオブジェクトとして作成したらあとはVBSでやるのとほぼ同じようにすればよい

$Acad = New-Object -ComObject AcadRemocon.body  
$dwgname = $null  
[void]$acad.acGetVar('DWGNAME',[ref]$dwgname,$False)  
Write-Host $dwgname  
[System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($Acad)|Out-Null  
[GC]::Collect()  

戻り値に[ref]がつくのは参照渡しとなるため。また存在しない変数に[ref]をつけられないので先に変数を作っている
[void]にキャストしてるのはAcadRemoconは必ずコマンドの実行の成否をT/Fで返してくるので非表示にするため。パイプラインでOut-Nullも同様の目的でやっている。
PowerShellではパイプラインで渡すと前段の処理が終了してパイプに渡されるまで待つので、確実に処理を実行させるときに有効
AcadRemoconは特に意識しないと同期を考えずに次々に実行するので各コマンドを必ずOut-NullかOut-Variableでパイプライン渡ししてやるとウェイト処理を入れずに済む。