Solucionado (ver solução)
Solucionado
(ver solução)
5
respostas

Chamando Script ps1 em c# console app

Olá eu criei meu script ps1 e consigo executar com sucesso. Agora gostaria de chamar esse script através de uma aplicação console app desenvolvida com c#. Encontrei uma solução conforme abaixo:

 string text = System.IO.File.ReadAllText(@"C:\temp\ASFscript.ps1");
 using (PowerShell PowerShellInstance = PowerShell.Create())
            {
        PowerShellInstance.AddScript(text);
            PowerShellInstance.Invoke();
                }

Porém meu script não está executando e o compilador não apresenta nenhum erro. Alguém tem algum artigo ou exemplo parecido com o que estou tentando desenvolver?

5 respostas

Opa, Alberto. O que você gostaria de fazer?

Você está executando o Script, mas não guarda o retorno de Invoke em nenhum lugar. Aqui montei um exemplo, onde capturamos o resultado da execução e mostramos na tela:

var SCRIPT =
@"
function Get-HelloWorld()
{
    ""Hello World""
}

# Como não capturamos em nenhuma variável, o resultado
# vai para o host dessa sessão - sua aplicação

Get-HelloWorld     # Chamada para função com retorno.
123                # Literal inteiro.
$True              # Valor de variável booleana.
$PSVersionTable    # Valor de variável complexa. ";


using (var PowerShellInstance = PowerShell.Create())
{
    Console.WriteLine("Adicionando script...");
    PowerShellInstance.AddScript(SCRIPT);

    Console.WriteLine("Executando...");
    Collection<PSObject>  resultado = PowerShellInstance.Invoke();

    Console.WriteLine($"Resultado com {resultado.Count} itens:");

    foreach (PSObject item in resultado)
    {
        Console.WriteLine($"-   {item}");
    }
}

Console.ReadLine();

Isso te ajuda? Abs.

O Script que estou fazendo não retorna valor, eu gostaria de criar um arquivo de saida JSON.

#Script Myscript.ps1
Get-Localuser |  Where-Object Enabled -Like True | Select-Object Name | ConvertTo-Json | Out-File c:\temp\UsersLocal.json

Que problema interessante, viu, Alberto?

Veja como cheguei na solução:

Se não quiser saber do processo, pule para minha próxima resposta, aqui só explico um pouco de como cheguei nisto, porque realmente achei muito interessante hehe

1.) Se o arquivo não foi criado, algum erro aconteceu e não ficamos sabendo. Os erros são acessíveis através do stream de erro:

Console.WriteLine($"HadErrors: {powerShellInstance.HadErrors}");
foreach (var item in powerShellInstance.Streams.Error)
{
    Console.WriteLine($"-   {item}");
}

Com isso, a saída na aplicação será:

HadErrors: True
-   The term 'Get-Localuser' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

Ou seja, o Get-Localuser não foi encontrado.

O módulo que define esse cmdlet é o Microsoft.PowerShell.LocalAccounts, então eu tentei o importar com Import-Module Microsoft.PowerShell.LocalAccounts e então não foi possível carregar esse módulo.

2.) O PowerShell possui uma versão chamada PowerShell Core que é uma versão multi plataforma, roda no Linux. Realmente, o Get-LocalUser não faz sentido no Linux e foi isso o que eu encontrei no GitHub do projeto: https://github.com/PowerShell/PowerShell/issues/952

Se você não quiser ler a discussão, fica aqui um resumo: o Get-LocalUser funciona no Windows, mas não funciona no Linux.

3.) Eu estou instalando o pacote System.Management.Automation.dll pelo NuGet e percebi que ele é "netstandard2.0", ou seja, é uma lib que roda nas duas plataformas. Pra baixar somente a versão Windows, criei um projeto .NET 4.0, instalei o pacote e o código funcionou.

4.) Mas, não vamos querer ficar com um projeto .NET 4.0, então eu descobri a diferença entre eles e é uma coisa que eu ainda não entendi muito bem.

Os passos para funcionar são ...

(fica na próxima resposta)

solução!

Os passos para funcionar são:

  • Crie um projeto no Visual Studio (pode ser a última versão do .Net Framework);
  • Instale com o nuget o pacote Install-Package System.Management.Automation.dll;
  • No gerenciador de soluções, vá no projeto, botão direito e "Unload Project";
  • Botão direito de novo no projeto, agora em Edit .csproj;
  • No arquivo de projeto, busque pelos elementos:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    ...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    ...
</PropertyGroup>
  • Neles, inclua o elemento <Prefer32Bit>false</Prefer32Bit>

Estes 2 elementos aqui, por exemplo:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <PlatformTarget>AnyCPU</PlatformTarget>
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug\</OutputPath>
  <DefineConstants>DEBUG;TRACE</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  <PlatformTarget>AnyCPU</PlatformTarget>
  <DebugType>pdbonly</DebugType>
  <Optimize>true</Optimize>
  <OutputPath>bin\Release\</OutputPath>
  <DefineConstants>TRACE</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>

Agora sua aplicação deverá funcionar. Eu testei com o código abaixo:

static void Main(string[] args)
{
    using (var ps = PowerShell.Create())
    {
        InvokeCommand(ps, @"Get-Localuser |  Where-Object Enabled -Like True | Select-Object Name | ConvertTo-Json | Out-File c:\temp\UsersLocal.json ");
    }

    Console.ReadLine();
}

private static void InvokeCommand(PowerShell ps, string script)
{
    ps.AddScript(script);
    var resultado = ps.Invoke();

    Console.WriteLine($"HadErrors: {ps.HadErrors}");
    foreach (var item in ps.Streams.Error)
    {
        Console.WriteLine($"-   {item}");
    }
    Console.WriteLine($"Resultado com {resultado.Count} itens:");
    foreach (PSObject item in resultado)
    {
        Console.WriteLine($"-   {item}");
    }

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine();
}

Você poderia testar aí e dizer se deu certo? De qualquer forma, isso tá me cheirando a algum bug e essa não é uma solução bacana - será que no futuro as pessoas do seu time de desenvolvimento irão lembrar disto? É muito trick.

Uma alternativa melhor seria usar a classe Process do .NET. Seu código seria algo como:

Process.Start("Powershell.exe", @"C:\temp\ASFscript.ps1");

O que você acha?

Perfeito !!! Funcionou corretamente.

Então, o projeto inicial era com Process.Start

Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.FileName = "C:\\Windows\\System32\\net.exe";
            myProcess.StartInfo.Arguments = "user";

Porém desta forma eu não consigo filtrar os usuários ativos e não consigo formatar. Foi quando comecei a fazer o curso de PowerShell e descobri os scripts mágicos. rrss

Eu tentei converter este projeto exatamente como sua sugestão porém tive o problema de :

ScriptGet-Localuser : O termo 'Get-Localuser' não é reconhecido como nome de cmdlet, função, arquivo de script ou programa
operável.
Aparente o mesmo problema que teve inicialmente, porém se for mais seguro posso tentar executa-lo instalando a mesma biblioteca e adicionando os parâmetros no arquivos de .csproj