Introdução

Aprenda noções básicas para trabalhar com o Microsoft PowerShell em projetos do Visual Studio com um pacote NuGet ou disparar um processo em projetos do .NET Core Framework usando a linguagem de programação C#.

Todos os exemplos de código mostrados estão disponíveis neste repositório Saltar .

Requisitos

  • Microsoft Visual Studio Saltar 2019
  • .NET Core Framework 5 Saltar ou posterior
  • Configurar projetos para C# 9 (consulte o seguinte Saltar)

O que é o PowerShell?

O PowerShell é uma solução de automação de tarefas de plataforma cruzada composta por um shell de linha de comando, uma linguagem de script e uma estrutura de gerenciamento de configuração. O PowerShell é executado no Windows, Linux e macOS.

Como uma linguagem de script, o PowerShell é comumente usado para automatizar o gerenciamento de sistemas. Ele também é usado para criar, testar e implantar soluções, geralmente em ambientes de CI/CD. O PowerShell é criado no .NET Common Language Runtime (CLR). Todas as entradas e saídas são objetos .NET.

Embora o C# seja uma programação robusta situada sobre o .NET Framework, há momentos em que a execução de comandos do PowerShell a partir de um processo é mais rápida do que chamar comandos do PowerShell de pacotes nativos do NuGet PowerShell. Em algumas organizações, a execução de comandos do PowerShell a partir de um pacote gerenciado pode violar as políticas da empresa, enquanto a execução dos mesmos comandos de um processo não o fará. Uma desvantagem de executar comandos do PowerShell a partir de um processo é mais trabalhosa quando há a necessidade de executar vários comandos abrangendo mais de uma linha.

Primeiros passos

Comece com one-liners, por exemplo, obtenha o endereço IP da máquina atual. Uma janela de terminal ou PowerShell e digite Invoke-RestMethod ipinfo.io/ip e pressione enter.

O IP é mostrado

image

Para traduzir isso em código que é executado em um projeto do Windows Form .NET Core.

01.namespace ProcessingAndWait.Classes
02.{
03.    public class PowerShellOperations
04.    {
05. 
06.        /// <summary>
07.        /// Get this computer's IP address synchronous
08.        /// </summary>
09.        /// <returns>Task<string></returns>
10.        public static async Task<string> GetIpAddressTask()
11.        {
12.            const string fileName = "ipPower_Async.txt";
13. 
14. 
15.            if (File.Exists(fileName))
16.            {
17.                File.Delete(fileName);
18.            }
19. 
20.            var start = new ProcessStartInfo
21.            {
22.                FileName = "powershell.exe",
23.                UseShellExecute = false,
24.                RedirectStandardOutput = true,
25.                Arguments = "Invoke-RestMethod ipinfo.io/ip",
26.                CreateNoWindow = true
27.            };
28. 
29. 
30.            using var process = Process.Start(start);
31.            using var reader = process.StandardOutput;
32. 
33.            process.EnableRaisingEvents = true;
34. 
35.            var ipAddressResult = reader.ReadToEnd();
36. 
37.            await File.WriteAllTextAsync(fileName, ipAddressResult);
38.            await process.WaitForExitAsync();
39. 
40.            return await File.ReadAllTextAsync(fileName);
41.        }
42. 
43. 
44.    }
45. 
46.}

  • fileName variável é onde os resultados são gravados.
  • ProcessStartInfo Saltar configura um processo para obter o endereço IP. Neste exemplo e naqueles que se seguem a este impede que uma janela apareça.
  • O processo começa com os resultados redirecionados para o nome do arquivo no marcador 1.
  • Uma vez feito, os resultados são lidos em uma cadeia de caracteres.

O caller atribui o endereço IP a uma TextBox.

1.private  void GetIpAddressVersion1Button_Click(object sender, EventArgs e)
2.{
3.    IpAddressTextBox1.Text = "";
4.    IpAddressTextBox1.Text = PowerShellOperations.GetIpAddressSync();
5.}

Se, por qualquer motivo, o código acima demorar muito tempo para ser executado (como a partir de um computador lento), o acima pode ser executado de forma assíncrona.

01.public class PowerShellOperations
02.{
03. 
04.    public static async Task<string> GetIpAddressTask()
05.    {
06.        const string fileName = "ipPower_Async.txt";
07. 
08. 
09.        if (File.Exists(fileName))
10.        {
11.            File.Delete(fileName);
12.        }
13. 
14.        var start = new ProcessStartInfo
15.        {
16.            FileName = "powershell.exe",
17.            UseShellExecute = false,
18.            RedirectStandardOutput = true,
19.            Arguments = "Invoke-RestMethod ipinfo.io/ip",
20.            CreateNoWindow = true
21.        };
22. 
23. 
24.        using var process = Process.Start(start);
25.        using var reader = process.StandardOutput;
26. 
27.        process.EnableRaisingEvents = true;
28. 
29.        var ipAddressResult = await reader.ReadToEndAsync();
30. 
31.        await File.WriteAllTextAsync(fileName, ipAddressResult);
32.        await process.WaitForExitAsync();
33. 
34.        return await File.ReadAllTextAsync(fileName);
35.    }
36.}

Suponha que mais informações sejam necessárias, o seguinte usa o pacote NuGet Json.net e uma classe de contêiner para fornecer mais detalhes.

Container

01.using Newtonsoft.Json;
02. 
03.namespace ProcessingAndWait.Classes.Containers
04.{
05.    public class IpItem
06.    {
07.        [JsonProperty("ip")]
08.        public string Ip { get; set; }
09.        [JsonProperty("city")]
10.        public string City { get; set; }
11.        [JsonProperty("country")]
12.        public string Country { get; set; }
13.        [JsonProperty("loc")]
14.        public string Location { get; set; }
15.        [JsonProperty("org")]
16.        public string Org { get; set; }
17.        [JsonProperty("region")]
18.        public string Region { get; set; }
19.        [JsonProperty("postal")]
20.        public string Postal { get; set; }
21.        [JsonProperty("timezone")]
22.        public string Timezone { get; set; }
23.        [JsonProperty("readme")]
24.        public string Readme { get; set; }
25. 
26.        public string Details => $"IP:{Ip}\nRegion: {Region}\nCountry: {Country}";
27.    }
28.}

JsonPropery fornece um alias para cada propriedade, pois json diferencia maiúsculas de minúsculas, enquanto as propriedades devem ser camel case. Para obter detalhes, use o seguinte, que indica um arquivo de saída.

01.public static async Task<IpItem> GetIpAddressAsJsonTask()
02.{
03.    const string fileName = "externalip.json";
04. 
05. 
06.    if (File.Exists(fileName))
07.    {
08.        File.Delete(fileName);
09.    }
10. 
11.    var start = new ProcessStartInfo
12.    {
13.        FileName = "powershell.exe",
14.        UseShellExecute = false,
15.        RedirectStandardOutput = true,
16.        Arguments = "Invoke-RestMethod -uri https://ipinfo.io/json Saltar -outfile externalip.json",
17.        CreateNoWindow = true
18.    };
19. 
20. 
21.    using var process = Process.Start(start);
22.    using var reader = process.StandardOutput;
23. 
24.    process.EnableRaisingEvents = true;
25. 
26.    var ipAddressResult = await reader.ReadToEndAsync();
27.    if (File.Exists(fileName))
28.    {
29.        var json = File.ReadAllText(fileName);
30.        return JsonConvert.DeserializeObject<IpItem>(json);
31.    }
32. 
33.    return new IpItem();
34.}

Retorna várias propriedades para manter as coisas simples.

image

Obtenção de serviços na máquina atual. Como acontece com qualquer linguagem, um comando do PowerShell pode acabar parecendo complexo, até que se familiarize com ele, o que é melhor feito não escrevendo comandos no código, mas em uma janela do PowerShell.

Aqui está o código para obter serviços.

01.public class PowerShellOperations
02.{
03.    public static async Task<List<ServiceItem>> GetServicesAsJson()
04.    {
05.        const string fileName = "services.txt";
06. 
07.        if (File.Exists(fileName))
08.        {
09.            File.Delete(fileName);
10.        }
11. 
12.        var start = new ProcessStartInfo
13.        {
14.            FileName = "powershell.exe",
15.            UseShellExecute = false,
16.            RedirectStandardOutput = true,
17.            Arguments = "Get-Service | Select-Object Name, DisplayName, @{ n='Status'; " +
18.                        "e={ $_.Status.ToString() } }, @{ n='table'; e={ 'Status' } } | ConvertTo-Json",
19.            CreateNoWindow = true
20.        };
21. 
22.        using var process = Process.Start(start);
23.        using var reader = process.StandardOutput;
24. 
25.        process.EnableRaisingEvents = true;
26. 
27.        var fileContents = await reader.ReadToEndAsync();
28. 
29.        await File.WriteAllTextAsync(fileName, fileContents);
30.        await process.WaitForExitAsync();
31. 
32.        var json = await File.ReadAllTextAsync(fileName);
33. 
34.        return JsonSerializer.Deserialize<List<ServiceItem>>(json);
35. 
36.    }
37. 
38.}

Container class (Json aliasing excluded)

01.public class ServiceItem
02.{
03.    public string Name { get; set; }
04.    public string DisplayName { get; set; }
05.    public string Status { get; set; }
06.    public string table { get; set; }
07.    public ServiceStartMode ServiceStartMode { get; set; }
08.    public override string ToString() => Name;
09.    /// <summary>
10.    /// For adding items to a ListView
11.    /// </summary>
12.    /// <returns></returns>
13.    public string[] ItemArray() => new[] { Name, DisplayName, Status };
14.}

Veja o projeto para o código de chamada, aqui está a saída.

image

Talvez outra opção seja apresentar serviços em uma página da web.

image

Código para o acima

  • A tupla de valor nomeado é usada para retornar ao chamador se o
    • A operação foi bem-sucedida
    • Um objeto de exceção por falha ao concluir a tarefa.
  • ChromeLauncher.OpenLink(nome_do_arquivo); aciona um navegador Chrome se instalado.
01.public static async Task<(bool, Exception)> GetServicesAsHtml()
02.{
03.    string fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "service.html");
04. 
05.    if (File.Exists(fileName))
06.    {
07.        File.Delete(fileName);
08.    }
09. 
10.    var start = new ProcessStartInfo
11.    {
12.        FileName = "powershell.exe",
13.        UseShellExecute = false,
14.        RedirectStandardOutput = true,
15.        Arguments = "Get-Service | Select-Object Name, DisplayName, @{ n='Status'; " +
16.                    "e={ $_.Status.ToString() } }, @{ n='table'; e={ 'Status' } } | ConvertTo-html",
17.        CreateNoWindow = true
18.    };
19. 
20.    using var process = Process.Start(start);
21.    using var reader = process.StandardOutput;
22. 
23.    process.EnableRaisingEvents = true;
24. 
25.    var fileContents = await reader.ReadToEndAsync();
26. 
27.    await File.WriteAllTextAsync(fileName, fileContents);
28.    await process.WaitForExitAsync();
29. 
30.    try
31.    {
32.        ChromeLauncher.OpenLink(fileName);
33.        return (true, null);
34.    }
35.    catch (Exception ex)
36.    {
37.        return (false, ex);
38.    }
39. 
40.}

Importância assíncrona

Todos, exceto um exemplo de código anterior, foram assíncronos, embora seja prudente executar de forma assíncrona, pode haver casos como consultar logs de eventos do Windows. Mesmo um dia de leitura de registros pode levar 30 ou mais segundos. Por esse motivo, considere tornar todas as chamadas assíncronas. Incluído no código-fonte, há um exemplo para obter o log de eventos de ontem. Uma exceção seria pedir x das entradas de log mais recentes, por exemplo,

1.Get-EventLog -LogName system -EntryType `Error` -Newest 50

Ao executar o projeto ProcessingAndWait, clique em cada botão sem esperar e, em seguida, mova o formulário na tela. Isso ocorre porque todo o processamento mantém o formulário responsivo. Escrever código que não é responsivo indica código mal escrito e, com isso, os clientes de um codificador/desenvolvedor perderão o respeito pelos codificadores/desenvolvedores.

Trabalhando com pacotes do PowerShell

Antes de começar, certifique-se de instalar o pacote adequado em seu projeto.

No Package Manager Console

PM> Install-Package Microsoft.PowerShell.SDK -Version 7.2.0-preview.4, sempre escolha a versão atual ou, como nesse caso, um pacote de visualização foi usado.

Se o pacote clássico do .NET Framework estiver selecionado, o gerenciador de pacotes tentará instalá-lo, mas falhará e reverterá a instalação.

Exemplo de código simples

Aqui estão vários exemplos de código para se familiarizar com o código gerenciado para PowerShell. Estes estão incluídos no código-fonte incluído.

01.public class PowerShellOperations
02.{
03.    public delegate void OnProcess(ProcessItem sender);
04.    /// <summary>
05.    /// Raised event when invoking a pipeline
06.    /// </summary>
07.    public static event OnProcess ProcessItemHandler;
08. 
09.    /// <summary>
10.    /// Synchronous processing
11.    /// </summary>
12.    public static void Example1()
13.    {
14.        var ps = PowerShell.Create().AddCommand("Get-Process");
15.        IAsyncResult pipeAsyncResult = ps.BeginInvoke();
16. 
17.        foreach (PSObject result in ps.EndInvoke(pipeAsyncResult))
18.        {
19. 
20.            ProcessItemHandler?.Invoke(new ProcessItem()
21.            {
22.                Value = $"{result.Members["ProcessName"].Value,-20}{result.Members["Id"].Value}"
23.            });
24.             
25.        }
26.    }
27. 
28.    /// <summary>
29.    /// Asynchronous processing
30.    /// </summary>
31.    /// <returns></returns>
32.    public static async Task Example2Task()
33.    {
34.        await Task.Run(async () =>
35.        {
36.            await Task.Delay(0);
37.            var ps = PowerShell.Create().AddCommand("Get-Process");
38. 
39.            var pipeAsyncResult = ps.BeginInvoke();
40. 
41.            foreach (var result in ps.EndInvoke(pipeAsyncResult))
42.            {
43.                 
44.                ProcessItemHandler?.Invoke(new ProcessItem()
45.                {
46.                    Value = $"{result.Members["ProcessName"].Value,-20}{result.Members["Id"].Value}"
47.                });
48. 
49.            }
50. 
51.        });
52. 
53.    }
54. 
55.    static readonly string ScriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "runner.ps1");
56.    /// <summary>
57.    /// Run a PowerShell script
58.    /// </summary>
59.    /// <returns></returns>
60.    public static string RunScript()
61.    {
62.        // create PowerShell runspace
63.        var runspace = RunspaceFactory.CreateRunspace();
64. 
65.        // open it
66.        runspace.Open();
67. 
68.        // create a pipeline and feed it the script text
69.        Pipeline pipeline = runspace.CreatePipeline();
70.        pipeline.Commands.AddScript(ScriptPath);
71.         
72.        pipeline.Commands.Add("Out-String");
73. 
74.        // execute the script
75.        var results = pipeline.Invoke();
76. 
77.        // close the runspace
78.        runspace.Close();
79. 
80.        // convert the script result into a single string
81.        StringBuilder stringBuilder = new();
82.         
83.        foreach (var psObject in results)
84.        {
85.            stringBuilder.AppendLine(psObject.ToString());
86.        }
87. 
88.        return stringBuilder.ToString();
89.    }
90.}

Prática

Mencionado anteriormente, não tente experimentar comandos no código. Primeiro, execute comandos em uma janela do PowerShell. Mesmo com isso, pode haver casos em que um comando ou conjunto de comandos pode falhar se uma política proibir a execução de scripts do PowerShell.

Consulte o seguinte arquivo de markdown para obter comandos comuns a serem experimentados.

Resumo

Com as informações apresentadas, um desenvolvedor pode começar a escrever comandos do PowerShell simples a complexos no Visual Studio usando C#.

Ver também

Introdução à documentação do PowerShell Azure PowerShell

Fonte


Artigo Original