Kevin Marques ★

Primeiras impressões com o PowerShell

· Kevin Marques

Minhas primeiras impressões em usar o PowerShell pela primeira vez. A linguagem de script moderna da Microsoft criada para o desenvolvedor moderno. Aqui eu discuto algumas diferenças entre a syntax da linguagem e comparo com o Bash, no final concluo qual deles é melhor para cada situação e por que.

thumb

Pois é, sucumbi ao desejo de experimentar um ferramental da Microsoft e acabai estudando um pouco o PowerShell, sei que é um ato de blasfêmia perante a comunidade que prega a liberdade e o coletivismo, mas meh. Brincadeiras a parte, eu tenho escutado muito que o “PowerShell é o shell para o desenvolvedor moderno” e que “[…] é fácil de aprender e prático de usar”, então decidi ver se é isso tudo mesmo, aproveitando que o Windows aqui está mais mofando enquanto não jogo Celeste.

Apesar de agora eu já ter uma base boa até, eu não comecei a estudar só por pura curiosidade, acredito que por vivermos em um mundo controlado por essas mega corps, aprender PowerShell não seja um investimento de tempo tão ruim. Tanto é que ele já foi útil no meu dia a dia, enquanto eu estava na escola, dei uma ajudinha para um amigo fazer o cálculo da distribuição gaussiana num dos PC’s da escola. Todo conhecimento é um investimento com saldo positivo na minha opinião, e eu precisava mudar um pouco de ar e sair do Linux para espiar o OS dos meus amigos e professores.

Enfim, tentarei não ser muito técnico e abordar os detalhes da linguagem nesse post, afinal, se você quer aprender, você pode fazer isso sozinho sem problema nenhum — a documentação é muito boa e têm vários tutorias/cursos por ai, mas mesmo assim listarei algumas das fontes que usei para pesquisar sobre a linguagem, talvez seja útil para você. Essas últimas semanas tem sido difíceis (sim, eu odeio estudar para o ENEM), portanto, tentarei ser o mais claro e objetivo nesse artigo.

Explorando a linguagem

Ok, antes de falar do óbvio, acho que é legal lembrar o porquê do PowerShell existir. Acho que muitos de nós [desenvolvedores] sempre enxergou o Command Prompt do Windows como o Bash deles, e, eu sei que tem toda a história por trás do Bash, mas mesmo assim, a experiência de usar o CMD não é a mesma coisa. Dá pra fazer coisas muito básicas, como gerenciar processos e lidar com o file system, mas que é complicado de usar é, até pra editar um texto pelo terminal não rola (problemas assim só são preocupantes quando o teu OS não está bootando).

Como a Microsoft, com sua sabedoria infinita, não gosta de jogar projeto fora, o CMD ainda permanece e irá permanecer conosco por muito tempo. Mas a produtividade dos desenvolvedores que usam Windows precisava evoluir logo. Daí surgiu a idea do PowerShell — o projeto tinha um outro nome no início e até acho que era open-source, mas não lembro —, um shell moderno e linguagem de script de uso prático para as mais diversas atividades, desde lidar com sessões de conexões remotas até o script de backup de arquivos mais ordinário.

Antes que fique muito propaganda, esse “moderno” só significa que o PowerShell foi criado pensando na produtividade do desenvolvedor sem se preocupar com coisas do tipo “esse comando precisa se chamar cd por que cahnge-dir ocupa 10 bytes na memória e é bLoAt”. Lógico que isso era uma preocupação séria no século passado, mas hoje não têm muito problema criar convenções de nome que facilitam o uso dos comandos.

Orientação a objetos e frescuras

Pois é, todo Cmdlet (já já eu explico) não só retorna um objeto na Pipeline para o próximo comando usar. De fato, o PowerShell foi construído em cima do .NET core e quase tudo nele é um objeto .NET, tanto é que todo objeto que você encontrar vai ter sempre o método .GetType() nele, por exemplo. Essa feature faz dele um shell cross-platform, dá pra usar ele no Linux se você quiser, sei lá porque.

Esses Cmdlets é o jeito do PowerShell de dizer “comando”. Mas dá pra definir melhor esses termos:

Um detalhe legal, o PowerShell tem a sua própria convenção de nomes para Functions e Cmdlets, e outras coisas. Os comandos sempre seguem o padrão de Verb-Noun, também reparei que os argumentos também são escritos em Camel Case, mas os operadores — agora não tem como defender, por algum motivo eles são parecidos com opções, como o -eq que é um ==, apesar de que o Bash também é assim — são escritos em minúsculos, o tempo todo.

E maioria desses comandos também tem a opção -WhatIf, que só mostra o output do comando na tela, sem executar nada por baixo. É interessante, mas nunca me vi na necessidade de usar essa opção até agora.

Falando sobre funções e variáveis

Aqui que as coisas começaram a ficar meio estranhas para mim. Parece que tudo gira em torno de ScriptBlocks, então o while (...) {...}, foreach (...) {...} e até a function ... {...} são abstrações para lidar com esses blocos de scripts entre chaves. Digo isso porque os ScriptBlocks podem fazer o uso de algumas variáveis automáticas que te ajudam a lidar com o que o usuário está passando para essa função.

Além disso, é possível definir uma função de, pelo menos, três jeitos diferentes. E elas podem ter três blocos dentre delas, que é o begin {...}, process {...} e o end {...}, que só são blocos que são executados no começo, meio e fim respectivamente.

 1function Weird-Function1 {
 2	$args   # Uma lista com os argumentos
 3	$input  # Objeto que o usuário vai passar pela Pipeline
 4}
 5
 6function Weird-Function2([VarType] $MyOption, [Parameter(ValueFromPipeline)] [VarType] $MyObj) {
 7}
 8
 9function Weird-Function3 {
10	param (
11		[VarType] $MyOption,
12		[Parameter(ValueFromPipeline)] [VarType] $MyObj
13	)
14
15	begin {
16		<# Bloco da função que será executado no começo,
17		normalmente vejo o pessoal colocar aqui a parte
18		do código que trata os dados antes de partir para
19		a execução direto #>
20	}
21
22	process {
23		<# Parte principal da função que vai fazer o que
24		ela precisa fazer logo, sem enrolação. Só lembrando
25		que o escopo das variáveis é compartilhado por
26		esses três blocos. #>
27	}
28
29	end {
30		<# E finalmente, aqui o pessoal limpa da memória
31		os objetos que foram usados e retorna para o usuário
32		o que deve ser retornado. #>
33	}
34}

Existe muita coisa que pode ser dita só sobre esse tópico — funções, variáveis, escopo, etc. — que abordar isso aqui é fugir totalmente do escopo do artigo. Mas vale pesquisar, mesmo se você não pretende trabalhar com Windows o tempo todo, é legal saber para não se perder na hora de ver o código de um colega de trabalho/escola.

Documentação

Eu precisava falar disso separadamente. É extraordinária, muito bem construída e o conceito de guardar um manual para tudo da linguagem em arquivos de texto é questionável, considerando que pode ter jeitos melhores de documentar um Cmdlet.

Parece que o PowerShell segue essa filosofia de o desenvolvedor não depender de conexão com a internet para saber como os conceitos, funções e comandos da linguagem funcionam. E tá, sei que você está pensando: “Mas isso é normal, existe o man, sabia?”. Mas o ponto é que as documentações são repletas de exemplos e são fáceis de entender. Quando você está com dúvida em algo, um Get-Help aqui, um .GetType() ali e um Get-Member acolá resolve.

Exemplo de código

A um tempo atrás eu fiz um script em Batch para selecionar alguns tipos específicos de arquivos e copiar para uma pasta. Eu tinha visto um vídeo do canal Fábrica de Noobs que vi quando era criança. Se me lembro bem, ele só mostrou por 5 segundos um script que copiava arquivos, ai eu pensei em fazer igual. Agora vou tentar convertê-lo para uma ferramenta usando o PowerShell.

Ok, esse primeiro trecho vai ditar quais elementos serão necessários para o script funcionar. Veja que só o -SelectFiles é obrigatório, vou explicar o porquê mais adiante:

1[CmdletBinding()] # Isso serve para o script se comportar como um Cmdlet (pesquise, to com preguiça...)
2
3param (
4    [String] $FromDir,
5    [String] $DestDir,
6    [Parameter(Mandatory)] [String] $SelectFiles = "*"
7)

Mas eu vou precisar do -FromDir e do -DestDir de qualquer forma, só que eu quero que apareça uma janela para o próprio usuário especificar essas pastas com os arquivos que ele quer copiar. Tá meio complicado agora, mas tente se esforçar para entender, eu criei uma função — Validate-DirectoryPathOptions — que muda o valor de determinada opção, e também pede um texto para mostrar na janela de seleção de arquivo.

1$FromDir = Validate-DirectoryPathOptions `
2    -Option $FromDir `
3    -Description "Selecione o diretório ORIGEM com os arquivos para serem copiados"
4
5$DestDir = Validate-DirectoryPathOptions `
6    -Option $DestDir `
7    -Description "Selecione o DESTINO para onde os arquivos devem ser copiados"

A função em si não é muito complicada, ela chama outras funções que só conferem se o usuário escolheu mesmo uma pasta, caso contrário, mostra uma janela de erro e para de executar o código. O detalhe é ver como o PowerShell permite usar classes e métodos de classes estáticas do .NET Core para desenhar essas janelas.

 1function Display-ErrorMessage([String] $Message) {
 2    [System.Windows.Forms.MessageBox]::Show(
 3        $Message,
 4        "Ocorreu um erro inesperado",
 5        [System.Windows.Forms.MessageBoxButtons]::OK,
 6        [System.Windows.Forms.MessageBoxIcon]::Error
 7    ) > $null
 8}
 9
10function Select-Directory([String] $Description) {
11    $OpenFileDialog = New-Object System.Windows.Forms.FolderBrowserDialog
12    $OpenFileDialog.Description = $Description
13    $status = $OpenFileDialog.ShowDialog()
14
15    if ($status -eq "OK") {
16        $OpenFileDialog.SelectedPath
17
18    } else {
19        throw "Empty selection"
20    }
21}
22
23function Validate-DirectoryPathOptions([String] $Option, [String] $Description) {
24    if ($Option -ne "") {
25        return $Option
26    }
27
28    try {
29        Select-Directory ` -Description $Description
30        return
31    }
32    
33    catch {
34        Display-ErrorMessage -Message "Nenhuma pasta foi selecionada..."
35        exit 1
36    }
37}

Agora, eu quero adicionar a feature de selecionar certos tipos de arquivos com aliasses, como :images: para selecionar *.png, *.jpg, *.gif, etc. Essa string com o tipo dos arquivos será escrita no comando com a opção -SelectFiles

 1function Validate-SelectionString([String] $Option) {
 2    $SelectionAliasses = @(
 3        @(":images:",     "*.png *.jpg *.jpeg *.gif *.ico *.svg *.bmp"),
 4        @(":videos:",     "*.mp4 *.avi *.mkv *.vmv *.vma *.mpg *.mpeg *.asf"),
 5        @(":music:",      "*.mp3 *.wav *.flaac *.acc *.m4a"),
 6        @(":text:",       "*.txt *.md *.docx *.pdf *.doc *.docm"),
 7        @(":office:",     "*.doc *.docx *.docm *.xlsx *.xlsm *.xltx *.pptx *.pppsx *.potx *.accdb *.mdb"),
 8        @(":word:",       "*.doc *.docs *.docx *.docm"),
 9        @(":excel:",      "*.xlsx *.xlsm *.xltl"),
10        @(":powerpoint:", "*.pptx *.ppsx *.potx"),
11        @(":acess:",      "*.accdb *.mdb"),
12        @(":web:",        "*.html *.htm *.php *.js *.aspx *.css *.cpp *.py *.json"),
13        @(":design:",     "*.psd *.indd *.pdf *.svg *.cdr *.ai *.aep *.aepx *.ppj"),
14        @(":system:",     "*.dll *.reg *.jar *.bat *.vbs *.ps1"),
15        @(":compressed:", "*.zip *.rar *.tar *.7z")
16    )
17
18    foreach ($alias in $SelectionAliasses) {
19        $Option = $Option -replace $alias
20    }
21
22    return $Option
23}
24
25$SelectFiles = Validate-SelectionString -Option $SelectFiles

Só preciso finalizar com o ROBOCOPY agora, poderia ter usado um Cmdlet do PowerShell mesmo, mas vou aproveitar dessa compatibilidade com alguns binários do CMD.

1ROBOCOPY $FromDir $DestDir $SelectFiles /s

Conclusão

Se me perguntarem se eu gosto de PowerShell, vou responder: “Yeah…”. Consigo ver que ele realmente modernizou algumas coisas, a ideia de ter uma convenção de nomes que deve ser respeitada é interessante. Mas ainda sinto que têm muita coisa que não há necessidade de ter 3 ou 4 soluções diferentes, além de ser meio lenta, coisa que eu nunca entendi o porquê. A linguagem é sim boa, é fácil e prática, mas dá pra fazer tanta coisa que deixa de ser uma linguagem de script e passa a ser quase uma linguagem de programação.

Eu queria que tivesse arrays em Bash, queria que todos os comandos seguissem o pattern de Verb-Noun, mas não é necessário, ainda mais a essa altura que todo desenvolvedor já está acostumado com a sintaxe. Acredito que o PowerShell é o ideal se você precisa fazer tasks repetitivas muito complicadas no Windows, se não forem complicadas, eu usaria Batch mesmo, já que a maioria está costumado.

Outra coisa legal da linguagem, é que ela é fácil de começar, no sentido do Windows já ter uma IDE para programar scripts em PowerShell. Existe vários aliasses que ajudam a migração do usuário de Bash/Batch se adaptar, como a presença do dir e do ls (ambos são o Get-ChildItem). A própria sintaxe dele se assemelha, em alguns pontos, ao Bash Script mesmo.

Sobre ser cross-platform, só vejo utilidade para quem cresceu usando o PowerShell e não consegue mudar de workflow de jeito nenhum, fora isso, é só uma consequência do .NET.

É uma linguagem que vale a pena explorar, mas não dedicaria minha vida só nela. Eu aprendi muito sobre a importância desses patterns de nome e como escrever documentações melhores, como me adaptar um ambiente [muito] estranho e como sobreviver num Windows, caso precise. Se quiser uma dica para começar com o PowerShell, apenas domine o Select-Object, ForEach-Object e ScriptBlocks que é sucesso.


Artigos externos que usei durante os meus estudos:

Talvez você queira rodar essas linhas para tirar algumas dúvidas:

1Get-Help about_Variables
2Get-Help about_Profiles
3Get-Help about_Execution_Polices
4Get-Help about_Preference_Variables
5Get-Help -Examples Get-WindowsOptionalFeature
6Get-Help about_Using
7Get-Help about_Regular_Expressions
8New-Object System.Security.Cryptography.SHA1Managed | Get-Member
9New-Object System.Text.StringBuilder | Get-Member

#Windows   #Programming   #Archived  

comments powered by Disqus