2011年3月31日木曜日

◆パラメータとパイプライン両方の入力に対応した関数を作る

Powershellのコマンドレットだとパイプライン入力と-InputObject引数に対応した物がよくある。
同様のものを関数で作ってみる。

まず、パイプライン入力の時にParamでパラメータを受け取るためには、[Parameter(ValueFromPipeline=$True)]属性を指定してやれば良さそうだ。

また、入力が配列の場合、パイプライン入力はProcessブロックで個々の処理が可能だが、パラメータ入力の場合は自分でループしてあげる必要がありそうだ。

というわけでサンプルとしてつくってみたのが以下だ。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027

function ftest{
 
#[CmdletBinding()]
  param
(
   
[Parameter(ValueFromPipeline=$True)]
    $arg1
  )
 
 
process
{
 
   
function proc($arg1
) {
     
Write-Host "arg1 = $arg1"
    }
   
   
if($arg1.
count){
     
$arg1 | %{proc $_
}
    }
else
{
     
proc $arg1
    }
   
  }
}


"■ Param Input"
ftest ("argstring1","argstring2")

"■ PipeLine Input"
("argstring1","argstring2") | ftest

なんとなく結果は良さそうだ。(これが汎用的かどうかはちょっと自信が無い)

■ Param Input
PS>ftest ("argstring1","argstring2")
arg1 = argstring1
arg1 = argstring2
PS>
PS>"■ PipeLine Input"
■ PipeLine Input
PS>("argstring1","argstring2") | ftest
arg1 = argstring1
arg1 = argstring2

--- 2014/8/1 -----

たまたま昔のこのブログを見る機会があったのだが、これって特に呼ばれるパターンを判定して処理をしなくても良さそう・・・。

バージョンによる違いなのか、当時なにか勘違いをしていたのか・・・。

以下にサンプルの雛形があるので参考に。

PowerTips

2011年3月29日火曜日

◆スリープ(Sleep)解除イベントを拾う

PCの世界でも節電が最重要課題だ。
スリープ機能を使えば節電出来るのは判っているが、経験上スリープを使うと様々な不具合に見舞われる。

メーカー製ノートPC等はハード的に対応が進んでいて問題は出づらいだろうがデスクトップPCになるとまだまだ?

特に追加機器を接続していたりすると顕著に地雷を踏む。

私もこのご時世なのでスリープ設定をしてみた。
Windows7はスリープに入るのも戻るのも非常に素早く快適だ。

っと思っていたのもつかの間、やはり不具合はやってきた。
ロジクールマウスにアサインしている機能が効かなくなる。(結構以前からある問題のようだ)
しかもどの機能が効かなくなるかはその時々で違う。

ん~、日本のために我慢して使うかとも思ったのだが、ここらへんは作業効率にシビアに効いてくる部分なのでかなりストレスが溜まる。

調べてみるとSetPoint(ロジクールのマウスウェア)を再起動してやれば直るらしい。

それなら、スリープ復帰のイベントを拾って再起動してあげればOKじゃん。

という訳でWMIでそれっぽい物を探す。
すると、まさにそれっぽいWin32_PowerManagementEventなんてのがある。

PS>Get-WmiObject -List *power*


   NameSpace: ROOT\cimv2

Name
----
Win32_PowerManagementEvent
CIM_PowerSupply
CIM_UninterruptiblePowerSupply
Win32_PerfFormattedData_PowerMet...
Win32_PerfRawData_PowerMeterCoun...

ネットでこいつを調べたところEventType=7がスリープ復帰(Resume)イベントらしい。
イベントの扱い方は以前PowerShell: ◆プロセスの開始終了を監視する(Register-WmiEvent、Get-Event)でやっているのでこいつを真似してスリープ復帰時にSetPointを再起動するスクリプトが以下である。

</TBOD
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029

#requires -version 2.0
#
# Sleep解除の監視
#


$query = "Select * From Win32_PowerManagementEvent where EventType=7"
Register-WmiEvent -query $query -sourceidentifier "Resume" 

try
{
   
While($true
){
       
$newEvent = Wait-Event
        $sp = Get-Process SetPoint
        $path = $sp.
path
       
$sp.
Kill()
           
       
Get-Event | Remove-Event
       
       
& $path
    }
}

catch
{
   
Write-Warning "以下のエラーが発生しました"
    $error[0]
}
finally
{
   
#イベントを削除し購読をやめる
    Get-Event | Remove-Event
    Get-EventSubscriber | Unregister-Event
}

あとはこれを、スタートアップなりスケジューラなりで実行して上げれば良い。
私は、キックする起動ファイルを作りたくなかったのでタスクスケジューラに登録した。
image

引数は以下のように指定してみた。
-WindowStyle Hidden -file "D:\PS\Sleep解除の監視.ps1"

 

その後確かめてみたらSetPoint自体は最新(Ver6.20)にバージョンアップしたら不具合は直っていた(^^;

2011年3月28日月曜日

◆文字列で抽出するFilterを作る

今日ネットを眺めていて以下のようなFilterを見つけた。
20110328133923

一見便利そうであるが、背景を理解せずに無闇につかうのもどうかと思うので、個人的な解説を述べる。(あくまでも個人的な)

本来Powershellではパイプラインで渡すのは、これまでのように文字列ではなくオブジェクトだよっていうのが一番の売りなので無闇に文字列にして処理するのはナンセンス。(当然これを作った人はそれを理解した上で文字列で検索すると便利なこともあるよと言っているのだが)

Powershell的に上記処理を行うのであれば、

PS>Get-Service | ?{$_.Status -like "Run*"}

Status   Name               DisplayName
------   ----               -----------
Running  AeLookupSvc        Application Experience
Running  AudioEndpointBu... Windows Audio Endpoint Builder
Running  AudioSrv           Windows Audio
Running  BFE                Base Filtering Engine
Running  BITS               Background Intelligent Transfer Ser...

とやるのが普通。

まぁ、それでもプロパティ名を調べるのが面倒とか、もともと文字列でしか結果が帰ってこないコマンドもあるので一律文字列で検索るような上記Filterは有効とは思う。

ただし、文字列を検索するならSelet-Stringという専用のコマンドレットの使用も検討したほうが良いだろう。
以下はByPropertyNameなパラメータを文字列で検索している。

PS>Get-Help dir -full | Out-String -Stream | Select-String -Pattern "ByPropertyName" -Context 7,0

      -LiteralPath <string[]>
          場所のパスを 1 つ以上指定します。Path と異なり、LiteralPath の値は入力したとおりに使用さ
字はありません。パスにエスケープ文字が含まれている場合は、単一引用符で囲みます。単一引用符で囲んだ
           シーケンスとして解釈されません。

          必須                         true
          位置                         1
          既定値
>         パイプライン入力を許可する   true (ByPropertyName)

      -Path <string[]>
          場所のパスを 1 つ以上指定します。ワイルドカードを使用できます。既定の場所は現在のディレク

          必須                         false
          位置                         1
          既定値
>         パイプライン入力を許可する   true (ByValue, ByPropertyName)

ByPropertyNameを含んだ行だけを表示しても何のパラメータか判らないので、該当行の前7行を一緒に表示させている。「>」付きで表示されている行が実際にヒットした行である。

当初のFilterをContext機能付きに書き換えてみた。

001
002
003
004
005
006
007

filter grep($keyword,$context=(0,0))
{
 
$_ | Out-String -Stream | Select-String -Pattern $keyword -Context $context
}

gsv | grep Running
Get-Help dir -Full | grep bypropertyname 8,0

2011年3月23日水曜日

◆ファイルのパスに使える文字かを調べる

ファイルのパスに使えない文字はSystem.IO.PathクラスのGetInvalidPathCharsメソッドで取得することが出来る。

image

特殊な文字がたくさんあるので[Regex]::Escapeでエスケープしてから正規表現で一致するか調べればOKだろう。

PS>$escInvalidChar = [Regex]::Escape($invalidChar)
PS>"abcd-J_(" -match "[" + $escInvalidChar + "]"
False
PS>"ab<cd-J_(" -match "[" + $escInvalidChar + "]"
True

2011年3月22日火曜日

◆Powershellのデバッグ機能

タイプミス等によるバグを見つけるときにPowershellのデバッグ機能を使うと簡単に見つかる。

Set-PSDebug –Strict

とやっておけば、値の設定されていない変数参照をエラーにしてくれる。
また、Set-StrictModeコマンドレットを使うともう少しきめ細かくエラーチェックが出来る。

Set-StrictMode -version 2.0

※以下はヘルプから説明の抜粋。
-- 初期化されていない変数 (文字列の初期化されていない変数を含む) への参照を禁止します。
-- オブジェクトに存在しないプロパティへの参照を禁止します。
-- メソッドを呼び出すための構文を使用した関数の呼び出しを禁止します。
-- 名前のない変数 (${}) を禁止します。

Versionに1.0を指定するとSet-PSDebug –Strict とほぼ同等のチェックを行ってくれる。

こんな便利な機能なら最初から指定しておけよ、っと思うかもしれないが、スクリプトによっては存在しないプロパティを参照しても無視してくれることでコーディングが簡便になる場合もあるので、必要に応じて使い分けるのがベターだろう。

2011年3月17日木曜日

◆Windows7からActiveDirectoryモジュールを使う

ダウンロードの詳細 : Windows 7 用のリモート サーバー管理ツールをインストールし、以下からActiveDirectoryモジュールを有効にする。

20110317140728

後はImport-ModuleでActiveDirectoryモジュールを読みこめばサーバー上と同様な管理作業が出来るようになる。(当然権限は必要だろうが)

◆Zipファイル(圧縮ファイル)を作る(改)

ちょっと修正と補足。
17行目はIO.PathクラスのChangeExtensionメソッドを使ったほうがスマートっぽい。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022

<#
.SYNOPSIS
  Zip圧縮ファイルを作る
.DESCRIPTION
  フォルダーまたはファイルを指定して同一フォルダーにZipファイルを作る
.EXAMPLE
  makezip.ps1 "D:\test"
  makezip.ps1 "D:\test\sample.txt"
#>

param
(
 
[parameter(Mandatory=$true,
    HelpMessage="圧縮したいファイル(フォルダ)のパスを指定してください。")]
   [ValidateScript({Test-Path $_})]
  $target
 
)

$targetobj = Get-Item  $target
#$zipfile = ($targetobj.fullname -replace $targetobj.Extension,"") + ".zip"
$zipfile = [IO.Path]::ChangeExtension($targetobj.fullname, ".zip")
Set-Content $zipfile ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$zip = (new-object -com shell.application).NameSpace($zipfile) 

$zip.CopyHere(
$target)

18行目の("$([char]0)" * 18))は雰囲気で判るとは思うが、Asciiコードがゼロのキャラクター(文字)を18個つなげるって事。

PS>("$([char]0)" * 18) -eq (([char]0).ToString() * 18)
True

2011年3月16日水曜日

◆CSVファイルを使ってActiveDirectoryユーザーを追加する

CSVファイルを読んでNew-ADUserコマンドレットにパイプすれば簡単に出来る、っと思ったのだが、色々とつまずいた。

1件ごとに追加するときにはNameパラメータを指定するだけで良かったのだがCSVから追加するときにはSamAccountNameパラメータも指定する必要があるようだ。(タイミング的な問題が絡んでいそうな・・・)

また、EnabledパラメータやPasswordNotRequiredパラメータのboolパラメータの指定が効いてくれない。ByPropertyNameパラメータなので普通に指定できるはずと思うのだが。
指定の仕方が悪いのか・・・。

仕方が無いのでSet-ADUserで追加設定することにした。

001
002
003
004
005
006
007
008
009
010
011
012
013
014

$header = "Name,SamAccountName,SurName,GivenName,Enabled,PasswordNotRequired"
$user1 = 'kubo,kubo,久保,裕也,1,1'
$user2 = 'takagi,takagi,高木,康成,True,True'
$user3 = 'tohno,tohno,東野,峻,$true,$true'

$arrayuser = Get-Variable user*

Set-Content -Path .\user.csv -Value $header -Encoding 
Unicode
0
..($arrayuser.Count - 1) |
 
 
%{Add-Content -Path .\user.csv -Value ($arrayuser[$_].value -join ","
) `
   
-Encoding Unicode
}
 

Import-Csv .\user.csv |  New-ADUser -PassThru |
 
 
Set-ADUser -Enabled $true -PasswordNotRequired $true

11行目迄はCSVファイルを作っているだけなので別の方法でCSVを手作りする場合には特に関係ないので読み飛ばして構わない。<!--EndFragment--

2011年3月15日火曜日

◆ActiveDirectoryユーザーを追加する(New-ADUser)

GUIでユーザーを追加するときは仮の初期パスワードを付けていたのだが(そうしないと怒られるので)以下のようにすると初期パスワード無しでも行けるようだ。
(GUIでも出来るのかもしれないが)

001
002
003
004
005
006
007
008
009
010

$param = @{
  Name 
= "haratatsu"
  Surname = "原"
  GivenName = "辰徳"
  Enabled = $true
  PasswordNotRequired = $true
  Path = "OU=MyOU,DC=hoge,DC=co,DC=jp"
}

New-ADUser @param

◆ActiveDirectoryパラメータ

PowerShell from Japan!! Blog « PowerShell 情報発信ブログにActiveDirectoryのパラメータを解説した便利な画像があったのでリンクしておく。

◆ボリュームラベルを設定する

こんな感じで良いのかな・・・。

$drive = gwmi win32_volume | ?{$_.driveletter -eq "E:"}
$drive.label = "Tempドライブ"
$drive.put()

2011年3月14日月曜日

◆ActiveDirectoryユーザーを表示(検索する) (Get-ADUser)

-filterパラメータに*を指定すると全ユーザーを表示してくれるようだ。

PS>Get-ADUser -Filter * | ft name,enabled -auto

name          enabled
----          -------
Administrator    True
Guest           False
minminnana       True
krbtgt          False
テスト 太郎      True


ダイレクトに表示するには、

PS>Get-ADUser  -identity "administrator" | ft name,enabled -auto

name          enabled
----          -------
Administrator    True


PS>Get-ADUser  "administrator" | ft name,enabled -auto

name          enabled
----          -------
Administrator    True

条件を指定するには、

PS>Get-ADUser -Filter {name -like "mi*"}


DistinguishedName : CN=minminnana,CN=Users,DC=mtg,DC=xxx,DC=co,DC=jp
Enabled           : True
GivenName         :
Name              : minminnana
ObjectClass       : user
ObjectGUID        : a2678468-1d8c-4dcb-9bba-0437f9efc448
SamAccountName    : minminnana
SID               : S-1-5-21-806116398-1360096813-3268469348-1000
Surname           :
UserPrincipalName :

色々なプロパティを参照するには、単純にgmすると

PS>Get-ADUser administrator | gm


   TypeName: Microsoft.ActiveDirectory.Management.ADUser

Name              MemberType            Definition
----              ----------            ----------
Contains          Method                bool Contains(string propertyName)
Equals            Method                bool Equals(System.Object obj)
GetEnumerator     Method                System.Collections.IDictionaryEnumerator GetEnumerator()
GetHashCode       Method                int GetHashCode()
GetType           Method                type GetType()
ToString          Method                string ToString()
Item              ParameterizedProperty Microsoft.ActiveDirectory.Management.ADPropertyValueCollection It
DistinguishedName Property              System.String DistinguishedName {get;set;}
Enabled           Property              System.Boolean Enabled {get;set;}
GivenName         Property              System.String GivenName {get;set;}
Name              Property              System.String Name {get;}
ObjectClass       Property              System.String ObjectClass {get;set;}
ObjectGUID        Property              System.Nullable`1[[System.Guid, mscorlib, Version=2.0.0.0, Cultur
SamAccountName    Property              System.String SamAccountName {get;set;}
SID               Property              System.Security.Principal.SecurityIdentifier SID {get;set;}
Surname           Property              System.String Surname {get;set;}
UserPrincipalName Property              System.String UserPrincipalName {get;set;}

こんなメンバーしか帰って来ないので、

PS>Get-ADUser administrator -Properties *

とやると全てのメンバーが表示できそうだ。

Mailアドレスに指定のあるユーザーを全て表示する。

PS>Get-ADUser -Filter {mail -like "*"} | select name,mail

name                                                        mail
----                                                        ----
minminnana
Administrator


とやりたくなるが、mailは標準では表示されないプロパティなので、

PS>Get-ADUser -Filter {mail -like "*"} -property mail | select name,mail

name                                                        mail
----                                                        ----
minminnana                                                  minminnana@hoge.co.jp
Administrator                                               testmail@hoge.co.jp

といった感じになる。

まぁ、ヘルプを見れば普通の事は出来るのではなかろうか。

2011年3月11日金曜日

◆イベントログを取得(抽出)する(Get-WinEvent)

イベントログを取得するにはGet-WinEventコマンドレットを使えば良い。
取ってきたログを抽出するのも普通にやれば問題ないのだが、全部取ってきてからでは時間が掛かる。

そんな時は、ログを取ってくる時点でFilterして上げれば良い。FilterHashtableオプションを使うのが簡単そうだ。

サンプルとして過去数カ月の発生頻度の高いエラーや警告をサマリーして抽出するスクリプトを作ってみた。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020

$levels = @{重大 = 1 ; エラー =2 ; 警告 = 3}
$lognames = "system","application"
$month = 10  #過去何ヶ月を表示するか

function getevent($logname,[ref]$reflevel
){
 
$level = $reflevel.
value
 
Write-Host <$logname $level.name -ForegroundColor Cyan
  Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable
 @{
    level 
= $level.
value
    logname 
= $logname
    starttime = ((get-date).addmonths(-$month
))
  } 
|
 
 
group id | sort count -des | select -first 5 |
 
 
select count,name,{$_.group[0].message} | ft -auto -Wrap
}

$lognames | %
{
 
$logname = $_
  $levels.GetEnumerator() | sort value | %{getevent $logname ([ref]$_) }
}

結果はこんな感じになる。
20110311105207

指定できる抽出条件は以下のような感じ。

20110311105655

また、Get-WinEventコマンドレットはコンピューター名も指定できるようなので、ドメイン管理者とかであれば、複数サーバーを巡回させてSystemCenter的な使い方もできそうだ。

---- 2015/3/3 --

Ver3以上であればHashTableからPSCustomObjectに変換して以下の様な感じでもOK
(本質的な部分ではないのでどうでもよいのだが、[ref」でやるのはちょっとイマイチな感じもあるので、個人的にメモしておく)
--

あっれー、違うっぽい?「PsCustomObject」は不要で、単に配列にしたからEnumerate不要になっただけ?

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021

$levels = [PsCustomObject]@{name="重大";value=1},
          [PsCustomObject]@{name="エラー";value=2},
          [PsCustomObject]@{name="警告";value=3}
$lognames = "system","application"
$month = 10  #過去何ヶ月を表示するか

function getevent($logname,$level
){
 
Write-Host <$logname $level.name -ForegroundColor Cyan
  Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable
 @{
    level 
= $level.
value
    logname 
= $logname
    starttime = ((get-date).addmonths(-$month
))
  } 
|
 
 
group id | sort count -des | select -first 5 |
 
 
select count,name,{$_.group[0].message} | ft -auto -Wrap
}

$lognames | %
{
 
$logname = $_
  $levels | sort value | %{getevent $logname ($_) }
}

2011年3月7日月曜日

◆ローカルユーザーグループのメンバー一覧を表示する

ローカルユーザーグループを取得し、それぞれのグループのメンバーを一覧表示する。

001
002
003
004
005
006
007
008
009
010

$strComputer = hostname
Get-WMIObject
 Win32_Group -filter "domain='$strComputer'" | %
{
 
$computer = [ADSI]("WinNT://" + $strComputer + ",computer"
) 
 
$Group = $computer.psbase.children.find($_.
name) 
 
$members= $Group.psbase.invoke("Members") | %
{
   
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null
)
  } 
 
Write-Host "<" $_.name "Group >" -ForegroundColor Yellow
  $members
}

結果
image

2011年3月1日火曜日

◆終了コードを取得する

終了コードについては自動変数である$lastexitcodeを参照すれば良い。

20110301100533

PS>D:\Desktop\test.ps1
PS>$lastexitcode
30
PS>D:\Desktop\test.bat
PS>$lastexitcode
20