2011年1月26日水曜日

◆共有ファイルの使用者を調べる

ファイルサーバーにあるOfficeファイルなどを使っていると他人が使っていて、参照モードでしか開けない事がある。

Officeのユーザー名に名前が設定してあれば誰とバッティングしているか判るのだが、設定していない人とバッティングするとひたすら待つしか無い。

そこでファイルの使用者を調べるスクリプトを作ってみた。
Openfiles.exeを使うとOpen中のファイルを取得できるので、これを使った。
(基本的には管理者権限が必要になってくると思うので使用できる人は限られるかもしれない)

001
002
003
004
005
006
007
008

#サーバーのOpen中ファイルを調べる
$filename = "*進捗.xlsx"

$file = "開いているファイル (パス\実行可能ファイル)"
openfiles /query /S ServerName /U domain\administrator /P Password /fo csv |
 
 
ConvertFrom-Csv |
 
 
?{$_.$file -like $filename} |
 
 
ft アクセス,$file -auto

◆コンピュータをShutdownする(Stop-Computer,Restart-Computer)

以前からShutdown.exeが使えたが、Powershellでは専用のコマンドレットが用意された。
Stop-Computerコマンドレットだ。
Stop-Computerと打つと、Windowsのスタートメニューからシャットダウンを選択したのと同じ動作をする。(未保存のドキュメントなどがあると確認ダイアログが表示される)

Stop-Computer –Force とすると未保存のドキュメントがあろうがなかろうが強制的にシャットダウンする。

ComputerNameパラメータを指定するとリモートPCのシャットダウンにも使える。
Stop-Computer –Comp HogePC –Credential (Get-Credential)

ログイン中のユーザーが居るとエラーになるので、その場合はForceスイッチを指定する。
(当然未保存のデータは無視される)

ComputerNameパラメータは配列が指定できるので複数のPCを一気にシャットダウンしたいときは便利だろう。

Stop-Computerコマンドレットは内部的にはWMIのWin32_OperatingSystemを使っているので、直接これを使っても同じことが出来る。

ちなみに、再起動にはRestart-Computerコマンドレットを使う。

2011年1月24日月曜日

◆WMIオブジェクトを探す

PowerGUIにはWMI Browserなる機能が有って、WMIオブジェクトの全体を俯瞰したり検索したり出来る。
以下は、CIMV2からdriverという名前でフィルターしてみた例だ。
20110124160238

色々探してみると面白いオブジェクトがあるかもしれない。

ちなみに、PowershellからWMIオブジェクトを検索するのは以下のような感じだ。

gwmi -List | Select-String driver

2011年1月21日金曜日

◆グループ集計(ブレーク集計)

PowerShell: ◆グループ化して最終データを生かすでGroup-Objectコマンドレットを使ったときにグループ集計もやってみようと思い、そのままになっていたので試してみようと思う。

そもそも、かつてはプログラムといえば「ソート、マッチング、ブレーク集計」するための物。
なのでグループ集計なんて簡単にできるのかと思ったのだが、意外と「これ」といったコマンドが見つからない。
仕方なく、ごねごねとやって見る。

ファイルサーバーで容量を食っているファイルはどんな種類のファイルかを見てみたかったので、まずはある拡張子のファイルを抽出して容量の合計を求める。

PS>(dir d:\documents -Filter *.xlsx -r | measure length -sum).sum /1kb
86.802734375

あとは、これを特定の拡張子だけではなく全てのファイルに対して行えば良い。

001
002
003
004
005
006
007
008
009
010
011

Dir d:\documents -r | ?{!$_.psiscontainer} | Group-Object Extension | %{
 
$len = $_ | select -ExpandProperty Group | measure -Sum -Property length
  $len = $len.sum / 1kb
  Add-Member -Type NoteProperty -Name Length -Value $len -Input $_ -PassThru
} |
 
   
sort length -Descending |
 
   
ft name,count,
@{
                    name
="length(kb)"
                    expression={$_.length.ToString("#,##0.0"
)}
                    alignment
="Right"
                    } -auto

20110121115837

もう少しすっきり書けても良いような気はするのだが・・・。

◆パイプライン入力のプロパティを使う

誰でも使っていると思うが、パイプライン入力を参照するには「$_」を使う。

PS>dir | %{$_.fullname}
C:\Users\minminnana\Desktop
C:\Users\minminnana\Documents
C:\Users\minminnana\Favorites
C:\Users\minminnana\Links
C:\Users\minminnana\test

これは次のような局面でも同様に使える。例えば、ファイルの最終アクセス年でグルーピング。

PS>dir d:\documents -r | Group {($_.LastAccessTime).Year}

Count Name                      Group
----- ----                      -----
  640 2010                      {Downloads, Fax, Integration S
   26 2011                      {SQL Server Management Studio,

Selectでも同様。

20110121154654

この場合、項目名のタイトルが($_.LastAccessTime).Yearとなるが、このタイトル名を指定するのが、これまでにも何度か出てきた集計プロパティという事になる。

PS>dir d:\documents -r | select name,@{name="Year";expression={($_.LastAccessTime).Year}}

◆Getコマンドレットの隠れたエイリアスを使う

Get-ChildItemコマンドレットのエイリアスを調べるには、次のようにすれば良い。

PS>Get-Alias -Definition Get-ChildItem

CommandType     Name                                                          Definition
-----------           ----                                                          ----------
Alias                   dir                                                           Get-ChildItem
Alias                   gci                                                           Get-ChildItem
Alias                   ls                                                             Get-ChildItem

エイリアスが定義されていない場合は以下のようになる。

PS>Get-Alias -Definition Get-Acl
Get-Alias : definition 'Get-Acl' を含むエイリアスは存在しないため、このコマンドは一致するエイリアスを見つけられません
発生場所 行:1 文字:10
+ Get-Alias <<<<  -Definition Get-Acl
    + CategoryInfo          : ObjectNotFound: (Get-Acl:String) [Get-Alias]、ItemNotFoundException
    + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

しかし、このGet-Aclコマンドレットには実はエイリアスが存在する。
「Acl」である。

どうも、Get-XXXXコマンドレットは全てGetを省略したXXXXがエイリアスになっているようだ。
なので、Get-ChildItemもGet-Aliasコマンドでは表示されてこないが、「ChildItem」だけで実行可能だ。
Get-ChildItemなどはdirとかlsを使ったほうが便利なのでChildItemを使うことはないだろうが、Get-Aclの様にエイリアスが定義されていないコマンドレットの場合は覚えておくと便利だろう。

◆SQL Server Powershell リソース

 

SQL Server PowerShell の概要

SQL Server PowerShell を使用した管理手法 第 1 回 概要編

SQL Server PowerShell を使用した管理手法 第 2 回 実践編

Server クラス (Microsoft.SqlServer.Management.Smo)

2011年1月20日木曜日

◆SQL Server PowerShellの環境を作る4

SQL Server PowerShellの環境を調査してきたが、リモート接続に難があったりしてあまり有用とは思えない。
たぶん、サーバー自身にタスクでスクリプトを仕掛けたりって使い方がメインになるのかな?

おそらくSQL Server PowerShellもSMOのラッパーだったりするのだろうから、今のところSMOを直接弄ったほうが使い勝手がよさげ。(環境的にもSMOをロードしておくだけだし)

というわけでSMOを使ったサンプルを。

001
002
003
004
005
006
007
008
009

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") 
$server = 
New-Object Microsoft.SqlServer.Management.Smo.Server("Servername\instancename")
$server.ConnectionContext.LoginSecure = $false #Windows認証の時はTrue
$server.ConnectionContext.Login = "username" 
$server.ConnectionContext.Password = "password" 
$db = $server.databases["pubs"]
$result = $db.ExecuteWithResults("select * from jobs") 
$result.tables | select -ExpandProperty rows

SQLを投げるだけであればPowerShell: ◆PowershellでDBアクセス3あたりと大差ないと思うのだが、Serverオブジェクトを使えば管理タスクは何でもできそう。

ちなみに、PowerShell: ◆SQL Server PowerShellの環境を作るで作ったDBの容量一覧のスクリプトも、このServerオブジェクトを使って同様に取得できる。(結局中身は同じだろうし)

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

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") 
$server = 
New-Object Microsoft.SqlServer.Management.Smo.Server("Servername\instancename")
$server.ConnectionContext.LoginSecure = $false 
$server.ConnectionContext.Login = "username" 
$server.ConnectionContext.Password = "password" 

$dbinfos = @()
$fmt = "#,##0.0"
 
#データベースの数だけ繰り返し
foreach ( $db in $server.
Databases )
{
 
$dbinfo = New-Object PsObject |
 
     
select データベース名,DB,空き, データ,インデクス
  #データベース名
  $dbinfo.データベース名 = $db.
Name
 
#DBの容量
  $dbinfo.DB = $db.Size.ToString($fmt) + "MB"
  #DBの空き容量
  $dbinfo.空き = ($db.SpaceAvailable / 1024).ToString($fmt) + "MB"
  #データの容量
  $dbinfo.データ = ($db.DataSpaceUsage / 1024).ToString($fmt) + "MB"
  #インデックスの容量
  $dbinfo.インデクス = ($db.IndexSpaceUsage / 1024).ToString($fmt) + "MB"
  $dbinfos += $dbinfo
}
$dbinfos | ft –Auto

なお、SMOのロードにAdd-Typeを使うとエラーになる(環境にもよりそうだが)。
Add-Typeはストロングネームを指定する必要があるとの事。

try { Add-Type -Assembly Microsoft.SqlServer.Smo }
catch { Add-Type -Assembly 'Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' }

詳しくは以下を参照。
CTP3/V2b - Add-Type -a "Microsoft.SqlServer.Smo" won't load SMO assemblies | Microsoft Connect

◆SQL Server PowerShellの環境を作る3

sqlps環境はPowershellのスパーセットなのかと思っていたら、どうも違うようだ。
色々と使えないコマンドがあったりして使い勝手が違う。
スーパーセットではなくサブセットなのね・・・。

そこで、通常のPowershell環境で以下のスクリプトを実行するとPowershell+SQLServer用Snapin環境となるようだ。
(MS資料からの転載)SQL Server PowerShell の実行

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
030
031
032
033
034
035
036
037

#
# Add the SQL Server Provider.
#


$ErrorActionPreference = "Stop"

$sqlpsreg="HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.SqlServer.Management.PowerShell.sqlps"

if (Get-ChildItem $sqlpsreg -ErrorAction "SilentlyContinue"
)
{
   
throw "SQL Server Provider for Windows PowerShell is not installed."
}
else
{
   
$item = Get-ItemProperty $sqlpsreg
    $sqlpsPath = [System.IO.Path]::GetDirectoryName($item.
Path)
}



#
# Set mandatory variables for the SQL Server provider
#

Set-Variable -scope Global -name SqlServerMaximumChildItems -Value 0
Set-Variable -scope Global -name SqlServerConnectionTimeout -Value 30
Set-Variable -scope Global -name SqlServerIncludeSystemObjects -Value $false
Set-Variable -scope Global -name SqlServerMaximumTabCompletion -Value 1000

#
# Load the snapins, type data, format data
#

Push-Location
cd
 $sqlpsPath
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
Update-TypeData -PrependPath SQLProvider.Types.ps1xml 
update-FormatData -prependpath SQLProvider.Format.ps1xml 
Pop-Location

こいつをドットソースで取り込むなり、Profileに入れておくなりしておけば以下のような感じで簡単にSQLを発行できるようになる。

PS>invoke-sqlcmd -Database pubs -Query "select * from jobs" | ft -auto

job_id job_desc                     min_lvl max_lvl
------ --------                     ------- -------
     1 New Hire - Job not specified      10      10
     2 Chief Executive Officer          200     250
     3 Business Operations Manager      175     225
     4 Chief Financial Officier         175     250
     5 Publisher                        150     250
     6 Managing Editor                  140     225
     7 Marketing Manager                120     200
     8 Public Relations Manager         100     175
     9 Acquisitions Manager              75     175
    10 Productions Manager               75     165
    11 Operations Manager                75     150
    12 Editor                            25     100
    13 Sales Representative              25     100
    14 Designer                          25     100

2011年1月19日水曜日

◆SQL Server PowerShellの環境を作る2

PowerShell: ◆SQL Server PowerShellの環境を作るではローカルのSQLServerに接続した。

SQLServerの管理となれば当然リモートからなのでリモート接続を試して見た。
のだが、これがうまくいかない・・・・。

MSの資料によるとSQLServer認証の場合は仮想ドライブを作ってそこに資格情報を結び付けろとある。

001
002

New-PSDrive mysql -PSProvider SqlServer `
-Root "SQLSERVER:\SQL\ServerName\Default" -Credential (Get-Credential)

ちなみにデフォルトインスタンスでもインスタンス名は省略できず、「Default」と指定する必要があるらしい。

ここで表示された資格情報ダイアログに、 sa/hoge とか入れてやれば良いはずなのだが、以下のようなエラーが出る。

警告: SQL Server サービスの情報を取得できませんでした。'ServerName' の WMI
への接続が次のエラーで失敗しました: アクセスが拒否されました。 (HRESULT
からの例外: 0x80070005 (E_ACCESSDENIED))

メッセージからするとWMIレベルでエラーになっているようだ。
確かに、自PCからサーバーへはWMIで接続はできない。(まぁ、普通そうでしょ)
WMIを使ってSqlServerに認証を投げるには2種類の認証を指定しなければダメっぽ。

いろいろ調べてみたがそんな方法はなさげ・・・。
やっと以下の情報にたどり着いた。
New-PSDrive with SQL Authentication internally is using Integrated Authentication | Microsoft Connect

ん~、結局バグかい・・・。
っていうか仕様自体が破綻していない?

試しにWMIでアクセス可能なPCに対して上記スクリプトを実行したところ接続に成功した。
とはいえ、そもそもWMIでアクセス可能だったらWindows認証で良いわけなんでほとんど意味が無い。

というわけでSQLServerプロバイダーは使う環境が限られてきそうだ。

なお、テストで他PCのExpressを使う場合はリモート接続を有効にする必要がある。
SQL Server 2008 Express にリモート接続

2011年1月18日火曜日

◆SQL Server PowerShellの環境を作る

SQL Server PowerShellとはPowershellからSQL Serverを管理するためのツールセット(実体は、いくつかのコマンドレットとプロバイダー、環境変数、型定義等)である。

SQL Server2008をインストールすると一緒にインストールされる。(R2の場合はインストール時の機能選択で管理ツールを選ぶとインストールされるようだ)
Expressでもwith Advanced Services 版をインストールすれば使える。

一番簡単な使い方はコマンドラインから「sqlps」と打ち込む。20110118140923

するとSQL Server用の機能が組み込まれたPowershellが起動する。
SQLプロバイダーがルートになっているのでdirコマンドを打つと以下のような内容が表示される。

20110118141648

ルートのsqlを潜っていくとDatabasesが現れる。
image

この中身をまた潜っていくとTableやColumnが現れる。

というように、SQLプロバイダーのおかげでSQL Serverも通常のファイルツリーの様にアクセスが可能になっている。

あとはこれらのオブジェクトを使って様々な情報を引き出せば良い。

以下を参考にサンプルを作ってみた。
SQL Server PowerShell を使用した管理手法 第 2 回 実践編

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

$server = get-item SQLSERVER:\sql\localhost\sqlexpress\databases

$dbinfos = @()
$fmt = "#,##0.0"
 
#データベースの数だけ繰り返し
foreach ( $db in $server.
Collection )
{
 
$dbinfo = New-Object PsObject |
 
     
select データベース名,DB,空き, データ,インデクス
  #データベース名
  $dbinfo.データベース名 = $db.
Name
 
#DBの容量
  $dbinfo.DB = $db.Size.ToString($fmt) + "MB"
  #DBの空き容量
  $dbinfo.空き = ($db.SpaceAvailable / 1024).ToString($fmt) + "MB"
  #データの容量
  $dbinfo.データ = ($db.DataSpaceUsage / 1024).ToString($fmt) + "MB"
  #インデックスの容量
  $dbinfo.インデクス = ($db.IndexSpaceUsage / 1024).ToString($fmt) + "MB"
  $dbinfos += $dbinfo
}
$dbinfos | ft
-Auto

結果
20110118152238

ちなみに、スクリプトファイルを実行するときは、
sqlps -noexit -command "d:\desktop\db.ps1"
の様に起動してあげれば良い。

2011年1月17日月曜日

◆文字列からWMIオブジェクトを作る

通常WMIオブジェクトは Get-WmiObject コマンドレットで作ることが出来る。<

001
002
003

#デフォルトプリンターを設定する
$printer = gwmi win32_printer | ?{$_.name -eq "FX mini Driver for XPS"}
$printer.SetDefaultPrinter()

別な方法として、文字列をWMIクラスにキャストなんてことも出来るようだ。

</
001
002
003
004
005

#デフォルトプリンターを設定する
([wmi]'win32_printer="FX mini Driver for XPS"').SetDefaultPrinter()

#Cドライブの容量を表示する
"Cドライブの容量" + ([int](([wmi]'Win32_LogicalDisk="C:"').size/1gb)) + "GB"

([wmi]'Win32_Service.Name="Spooler"').StartMode

2011年1月14日金曜日

◆環境変数を更新する

環境変数を参照するには、 dir  env:

個々の変数は、 $env:temp とかやると参照可能。

$env:temp = “c:\temp” とかやれば値の設定も可能なのだが、その値は設定したPowershellセッションでしか有効にならない。
Profileに設定しておけばすべてのPowershellセッションで有効になるが、Powershell以外では無効。

永続的に更新するには以下のようにすると良さそうだ。

[Environment]::SetEnvironmentVariable(“temp”, “D:\temp”, “User”)

2011年1月12日水曜日

◆パラメータ属性

Powershellのパラメータには様々な属性が用意されている。
例えば、あるパラメータを必須にしたければParameter属性のMandatory引数を指定すれば良い。指定の仕方はこんな感じだ。

001
002
003
004
005
006

Param(
[parameter(Mandatory=$true)]
[string]$name
)

Write-Host $name

nameパラメータを指定せずに実行すると以下のようなプロンプトが表示される。
20110112162751

合わせてHelpMessage引数を指定すると簡易ヘルプを表示させることもできる。

001
002
003
004
005
006
007

Param(
[parameter(Mandatory=$true,
HelpMessage="あなたの名前を入力してください")]
[string]$name
)

Write-Host $name

20110112163617 <!--EndFr

 

必須パラメータの引数にNullを許可するようにするにはAllowNull属性を使う。

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

Param(
 
[parameter(Mandatory=$true)]
  [object]
  [AllowNull()]
  $obj
)

if($obj
){
 
Write-Host $obj.
GetType()
}
else
{
 
Write-Host "obj = null"
}

AllowNull属性を指定せずにパラメータにNullを指定した場合は以下のようなエラーとなる。
20110113115356

似たようなものに、Stringの必須引数に空の文字列を許可するAllowEmptyString属性などがある。

その他にはValidation系の属性がいくつか揃っている。
たとえば、ValidateLength属性は文字列の長さを限定できる。

001
002
003
004
005
006
007
008
009

Param(
 
[parameter(Mandatory=$true)]
  [string]
  [ValidateLength(1,10)]
  $name
)

{
以下のパラメータが指定されました}
Write-Host $name
<
!-20110113144619 -End

ValidatePattern 属性は引数の検証パターンを正規表現で指定する。

001
002
003
004
005
006
007
008
009

Param(
 
[parameter(Mandatory=$true)]
  [string]
  [ValidatePattern("\d{3}-\d{3}-\d{4}")]
  $phone
)

{
以下のパラメータが指定されました}
Write-Host $phone
End

20110113145620

他にも色々と属性はあるので詳細はabout_Functions_Advanced_Parametersを参照すると良い。

2011年1月11日火曜日

◆SendKeyを使う

アプリケーションを制御する方法としてSendKeyでキーストロークをアプリケーションに送る方法がある。
タイミングなども関係してくるのであくまでも簡便的な方法ではあるが、それなりに使える局面はあるとおもう。

という事でSendKeyのサンプルを作ってみた。
電卓を起動し、計算をさせて結果をメモ帳にコピーペーストしている。

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

add-type -AssemblyName System.Windows.Forms

$calc = Start-Process Calc -PassThru
start-sleep -Milliseconds 300

[System.Windows.Forms.SendKeys]::SendWait("2{*}3=")
[System.Windows.Forms.SendKeys]::SendWait("^(C)")

notepad
start-sleep
 -Milliseconds 300
[System.Windows.Forms.SendKeys]::SendWait("^V")
$calc.Kill()

キーストロークの詳細は以下を参照。
SendKeys メソッド

2011年1月5日水曜日

◆パイプライン入力(ByPropertyName)

コマンドレットのパラメータでパイプライン入力を許可するものの中にByValueとByPropertyNameの2種類がある。
ちなみに、以下はSet-VariableコマンドレットのValueパラメータのヘルプ表示である。20110105162227

ByValueというのは通常のパターン。
”test” | Set-Variable  var
とやると$varという変数に”test”という値が設定される。

ByPropertyNameというのはパイプライン入力オブジェクトのプロパティ名とSet-Variableのパラメータを名前でマッピングしてくれるものだ。
例えば、Set-VariabaleコマンドレットのNameとValueというパラメータはパイプライン入力オブジェクトにNameとValueというプロパティがあるとそれらが使用される。
以下のような感じだ。

001
002
003
004
005
006

$obj = New-Object PsObject | select Name,Value
$obj.Name = "住所"
$obj.Value = "神奈川県川崎市"
$obj | Set-Variable
$住所


20110112114640

ん~、結果を見るとちょっと期待と違っている。
NameはOKなのだが、Valueのほうは$objがそのまま設定されている。
これはByValueの動きだ。
もしかして、ValueパラメータのパイプラインにByPropertyNameとあるのはヘルプの誤り?
(もしくは私の理解が若干違うのかもしれない)

2011年1月4日火曜日

◆カスタムオブジェクト(PsObject)を使う(New-Object)2

PowerShell: ◆カスタムオブジェクト(PsObject)を使う(New-Object)ではNew-ObjectコマンドレットでPsobjectを作った。

プロパティの追加にはAdd-Memberコマンドレットを使ったり、集計プロパティを使ったり、ハッシュテーブルでアサインしたりと色々な方法があるが、以下のような方法もある。
(個人的にはこれが一番覚えやすく簡単に思える)

001
002
003

$obj = New-Object PsObject | select name,value
$obj.name,$obj.value = "Address","盛岡市大通4-7"
$obj | ft * -AutoSize

結果はこんな感じ。
20110104165150