サーバーの束からディスク容量レポートを取得し、それらをSQLテーブルに挿入しようとしています。
以下は私がやろうとしていることのサンプルです、サーバー名はテーブルから読み込まれ、カンマ区切りのリストとして渡されます。1つのサーバーを渡しても同じ結果になります
set @ps = 'powershell.exe -noexit -c "
$computers=Get-WmiObject -Class Win32_Volume '+@servernames+'
foreach($computer in $computers)
{
$pscomputername=$computer.pscomputername
$name=$computer.name
$capacity=$computer.capacity
$freespace=$computer.freespace
$Label=$computer.Label
$insertquery="
INSERT INTO [dbo].[temp_disksdata]
(
[servername] ,
[DiskName] ,
[Capacity(GB)] ,
[FreeSpace(GB)],
[label]
)
VALUES
(''$pscomputername'' ,
''$name'' ,
''$capacity'',
''$freespace'',
''$Label''
)
GO
"
Invoke-SQLcmd -query $insertquery -ServerInstance ''someserver'' -Database dbname
}
"';
今、私は以下のように変数をxp_cmdshellに渡そうとします
execute xp_cmdshell @ps;
SSMSで呼び出されると、上記はnullを返しますが、Powershellでは機能します。
なぜ何かアイデア?
以下は私が試したいくつかのことです
1.同じアカウント(admin)がシェルとssmsの両方で使用されています
2。インポートモジュールのような複数のことを試みた
3.XP_CMDshellは機能しますが、これはこのクエリに対してのみnullを返します
4。-nowaitを追加しようとしましたが、それも役に立ちません
私はこれを1日以上から成し遂げようとしましたが、これは機能しません.. ac#アプリを書くことが許可されていないため、xp_cmdshellを使用する必要があります。サーバー名がコンマとして渡されるため、BATファイルは役に立ちません区切りリスト。
コードを変更していて、尋ねられたときに書き換えないように言われました
さらに情報が必要な場合はお知らせください
再現:
以下は、printからのコマンド全体です。powershell.exeと-cを削除すると、以下がpowershellで実行されます。
powershell.exe -c "
$computers=Get-WmiObject -Class Win32_Volume 'servername'
foreach($computer in $computers)
{
$pscomputername=$computer.pscomputername
$name=$computer.name
$capacity=$computer.capacity
$freespace=$computer.freespace
$Label=$computer.Label
$insertquery="
INSERT INTO [dbo].[temp_disksdata]
(
[servername] ,
[DiskName] ,
[Capacity(GB)] ,
[FreeSpace(GB)],
[label]
)
VALUES
('$pscomputername' ,
'$name' ,
'$capacity',
'$freespace',
'$Label'
)
"
Invoke-SQLcmd -query $insertquery -ServerInstance 'servername' -Database dbname
}
"
ここにはいくつかの問題があります:
主な問題は、CMDシェルがリターン経由で入ってくる各行を実行することです。かどうかを知る方法がないため、;
などの「コマンドの終了」インジケーターを待機せず、解析によって(SQLがバッチの送信時に行うように)、それを理解しようとはしません。 return 「コマンド」を終了するかどうか。通常は^
を行の継続に使用できますが、これは入力パラメーター(少なくとも閉じられていない引用符のあるパラメーター)内では機能しないようです。
したがって、このためにすべてを1行に減らす必要があります。キャリッジリターン(13文字目)を空の文字列に、ラインフィード/改行(10文字目)をスペースに置き換えるだけで十分です。ただし、PowerShellステートメントは改行またはセミコロンで区切る必要があるため、それだけでは機能しません。現在は改行が使用されていますが、スペースで置き換える場合はセミコロンが必要になります。
したがって、ステップ1では、実際の各PowerShellコマンドにセミコロンを追加します。これは、T-SQLの場合と同じように、おそらく一般的には適切な形式です。
ステップ2では、@ps
変数に対して2つのREPLACE
呼び出しを実行して、これを1行のスクリプトに変換します。
INSERT
ステートメントの作成に使用される二重引用符が埋め込まれています。それらはバックスラッシュ(\
)でエスケープする必要があります。
Get-WmiObject
は、ここで使用しようとしているため、機能していないようです。システムの名前を渡すには、-ComputerName
を追加する必要がありました。空白のままにすると、ローカルシステム情報が返されますが、リモートサーバーの情報を返す場合は、-ComputerName
スイッチを使用して、一度に1つのサーバーを渡す必要があります。ほとんどの場合、複数のサーバー名を処理するために追加の外部ループが必要になります(リストを反復するために@ServerNames
を渡して分割することもできます)。
「容量」と「空き容量」は本当に文字列としてDBに保存されますか?そうでない場合は、INSERT
ステートメントでそれらの「値」を囲む単一引用符を削除することをお勧めします。
以下は少なくとも実行されます。そこからデバッグできます。 「Invoke-SQLcmd」モジュールなどが見つからないというエラーが発生しました。
DECLARE @ServerNames NVARCHAR(4000);
SET @ServerNames = N'ALBRIGHT';
DECLARE @ps NVARCHAR(4000);
SET @ps = N'powershell.exe -noexit -c "
$computers=Get-WmiObject -Class Win32_Volume -ComputerName ' + @ServerNames + N';
foreach($computer in $computers)
{
$pscomputername=$computer.pscomputername;
$name=$computer.name;
$capacity=$computer.capacity;
$freespace=$computer.freespace;
$Label=$computer.Label;
$insertquery=\";
INSERT INTO [dbo].[temp_disksdata]
(
[servername] ,
[DiskName] ,
[Capacity(GB)] ,
[FreeSpace(GB)],
[label]
)
VALUES
(''$pscomputername'' ,
''$name'' ,
''$capacity'',
''$freespace'',
''$Label''
)
GO
\";
Invoke-SQLcmd -query $insertquery -ServerInstance ''someserver'' -Database dbname;
}
"';
SET @ps = REPLACE(REPLACE(@ps, NCHAR(13), N''), NCHAR(10), N' ')
PRINT @ps;
EXEC xp_cmdshell @ps;
問題は、実行しようとしているコマンドの改行である可能性があります。以下のケースの違いを考慮してください。
-- case 1
execute xp_cmdshell N'powershell.exe -noexit -c "echo hello"';
-- case 2
execute xp_cmdshell N'powershell.exe -noexit -c "
echo hello
"';
xp_cmdshell
から複数行のpowershell
スクリプトを実行するには、コマンド全体をファイルに保存してから、1行の呼び出しでファイルを参照してください。たとえば、次のテキストが与えられた場合...
$srvs =@(
'localhost',
'127.0.0.1',
'(local)'
)
$srvs | % {
(Invoke-SqlCmd -ServerInstance "$_" -Query 'select @@servername srv').srv
}
...ファイルC:\temp\foo.ps1
として保存され、次の操作は成功するはずです...
execute xp_cmdshell N'powershell.exe c:\temp\foo.ps1';
T-SQLを使用してファイルシステムに任意のテキストを書き込む方法はいくつかありますが、より賢いプレイでは、静的ファイルがあり、DBへの別の呼び出しから@servernames
を取得する可能性があります。たとえば、これを変更します...
$computers = Get-WmiObject -Class Win32_Volume '+@servernames+'
...これをSQLスクリプトで...
$conn = @{
ServerInstance = $server
Database = $database
}
$servernames = (Invoke-SqlCmd @conn -query "select Name from server_list").Name -join ','
$computers = Get-WmiObject -Class Win32_Volume '$servernames'
... PowerShellスクリプトで。
素晴らしい dbatools Powershellモジュールを使用することを強くお勧めします。
このモジュールをインストールするだけです:
Install-Module dbatools
次に Get-DbaDatabaseSpace 関数を使用します。
# Get Db Free Space AND write it to table
Get-DbaDatabaseSpace -SqlInstance $instance | Out-GridView
Get-DbaDatabaseSpace -SqlInstance $instance -IncludeSystemDB | Out-DbaDataTable | Write-DbaDataTable -SqlInstance $instance -Database tempdb -Table DiskSpaceExample -AutoCreateTable
Invoke-Sqlcmd2 -ServerInstance $instance -Database tempdb -Query 'SELECT * FROM dbo.DiskSpaceExample' | Out-GridView
または、乗算インスタンスの場合:
$allservers = "localhost\sql2016", "localhost\sql2017"
$allservers | Get-DbaDatabaseSpace -IncludeSystemDB | Out-DbaDataTable | Write-DbaDataTable -SqlInstance $instance -Database tempdb -Table DiskSpaceExample -AutoCreateTable
また、このタスクには非常に便利なPowerBIテンプレートを使用できます。 https://sqljana.wordpress.com/2018/04/30/sql-server-quick-space-file-layout-analysis-with-powershell- and-powerbi /