Estou tentando obter o relatório de espaço em disco de vários servidores e inseri-los na tabela sql ..
abaixo está uma amostra do que estou tentando fazer, os nomes dos servidores serão preenchidos a partir de uma tabela e passados como uma lista separada por vírgulas. Mesmo passando um servidor também tem o mesmo resultado
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
}
"';
Agora eu tento passar a variável para xp_cmdshell como abaixo
execute xp_cmdshell @ps;
Quando invocado no SSMS, acima retorna nulo, mas funciona no powershell.
Alguma idéia por quê?
Abaixo estão algumas coisas que eu tentei
1.A mesma conta (admin) é usada tanto no shell quanto no ssms
2.tentei várias coisas como importar módulos
3.XP_CMDshell funciona, mas isso retorna nulo apenas para esta consulta
4.Eu tentei adicionar -nowait, mas isso não funciona ajude também
Eu tenho tentado fazer isso por mais de um dia, mas isso não funciona .. Eu tenho que usar xp_cmdshell porque escrever ac # app não é permitido. O arquivo Bat não ajuda porque os nomes dos servidores são passados como uma vírgula lista separada.
Estou modificando o código e me disseram para não reescrever quando solicitado
Por favor, deixe-me saber se você precisar de mais informações
Repro:
Abaixo está todo o comando da impressão, se você remover o powershell.exe e -c , abaixo será executado no 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
}
"
Há vários problemas aqui:
O principal problema é que o shell CMD executa cada linha à medida que ela chega por meio de um retorno; ele não espera por um indicador de "fim de comando", como
;
, nem tenta descobrir por meio de análise (como o SQL faz quando um lote é enviado), pois não há como saber se umreturn termina um "comando" ou não. Normalmente você pode usar^
para continuação de linha, mas isso não parece funcionar dentro de um parâmetro de entrada (pelo menos não um com aspas não fechadas).Então, para isso, você precisa reduzir tudo a uma única linha. Isso é bastante simples, pois você pode substituir Carriage Return (caractere 13) por uma string vazia e Line Feed/Newline (caractere 10) por um espaço. Mas isso por si só não funcionará, pois as instruções do PowerShell precisam ser separadas por uma nova linha ou um ponto e vírgula. Atualmente, as novas linhas estão sendo usadas, mas se as substituirmos por espaços, serão necessários pontos e vírgulas.
Portanto, a Etapa 1 é anexar um ponto e vírgula a cada comando real do PowerShell. Isso provavelmente é uma boa forma em geral, da mesma forma que com o T-SQL.
A etapa 2 seria fazer as duas
REPLACE
chamadas na@ps
variável para transformá-la em um script de linha única.Você inseriu aspas duplas, usadas para criar a
INSERT
instrução. Esses precisam ser escapados com uma barra invertida (\
).Get-WmiObject
não parece funcionar como você está tentando usá-lo aqui. Eu tive que adicionar-ComputerName
para passar no nome de um sistema. Se eu deixar em branco, ele retornou as informações do sistema local, mas se você quiser retornar informações para servidores remotos, provavelmente precisará usar o-ComputerName
switch e passar um servidor por vez. Isso provavelmente exigirá um loop externo adicional para lidar com vários nomes de servidor (e seria lá que você passaria@ServerNames
para ser dividido para iterar na lista)."capacity" e "freespace" são realmente armazenados como strings no banco de dados? Caso contrário, você pode querer remover as aspas simples em torno desses "valores" na
INSERT
instrução.O seguinte pelo menos é executado. Você pode depurar a partir daí. Eu estava recebendo erros por não encontrar o módulo "Invoke-SQLcmd" ou algo assim.
O problema pode ser os feeds de linha no comando que você está tentando executar. Considere a diferença entre os seguintes casos.
powershell
Para executar scripts de várias linhas dexp_cmdshell
, tente salvar o comando inteiro em um arquivo e, em seguida, fazer referência ao arquivo em uma chamada de linha única. Por exemplo, dado este texto......salvo como arquivo
C:\temp\foo.ps1
, o seguinte deve ser bem-sucedido...Existem várias maneiras de escrever texto arbitrário no sistema de arquivos usando T-SQL, mas a jogada mais inteligente provavelmente terá um arquivo estático e obterá o
@servernames
de outra chamada para o banco de dados - por exemplo, alterando isso ......em um script sql para isso...
... em um script powershell.
Altamente recomendado para usar dbatools incríveis Powershell.
Basta instalar este módulo:
Em seguida, use a função Get-DbaDatabaseSpace :
Ou para instâncias de multiplicação:
Além disso, você pode usar o modelo PowerBI muito útil para esta tarefa: https://sqljana.wordpress.com/2018/04/30/sql-server-quick-space-file-layout-analysis-with-powershell-and-powerbi/