Forçar saída do script Powershell no formato tabular

1

Existe alguma maneira de forçar a saída de um script do PowerShell v3 para um formato tabular? Meu script está exibindo uma lista de serviços de forma linear, embora haja apenas 6 campos no objeto de saída (o resultado do processo gera 8 campos em forma de tabela). Aqui está o meu código:

<#
.SYNOPSIS
Gets a list of services on a given computer that are supposed to automatically start but are not currently running.
.PARAMETER ComputerName
The computer name(s) to retrieve the info from.
.PARAMETER IgnoreList
The path and filename of a text file containing a list of service names to ignore.  This file has to list actual service names and not display names.  Defaults to "StoppedServices-Ignore.txt" in the current directory.
.PARAMETER StartServices
Optional switch that when specified will cause this function to attempt to start all of the services it finds stopped.
.EXAMPLE
Get-StoppedServices -ComputerName Computer01 -IgnoreList '.\IgnoredServices.txt' -StartServices
.EXAMPLE
Get-StoppedServices –ComputerName Computer01,Computer02,Computer03
.EXAMPLE
"Computer01" | Get-StoppedServices
.EXAMPLE
Get-StoppedServices –ComputerName (Get-Content ComputerList.txt)
.EXAMPLE
Get-Content ComputerList.txt | Get-StoppedServices -IgnoreList '.\IgnoredServices.txt' -StartServices
#>
Function Get-StoppedServices {
  [CmdletBinding()]
  param(
    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [String[]]$ComputerName,
    [string]$IgnoreList,
    [switch]$StartServices
  )
  PROCESS {
    # Load the list of services to ignore (if specified).
    if ($IgnoreList) {
      if (Test-Path $IgnoreList) {
        $ignore = import-csv -header Service $IgnoreList
        Write-Verbose "Ignoring the following services:"
        Write-Verbose $ignore.ToString()
      } else {
        Write-Warning "Could not find ignore list $IgnoreList."
      }
    }

    # Get a list of stopped services that are set to run automatically (ie: that should be running)
    foreach ($c in $ComputerName) {
      Write-Verbose "Getting services from $($c.Name)"
      if (Test-Connection -ComputerName $c -Count 1 -Quiet) {
        Try {
          $serv += get-wmiobject -query "Select __Server,Name,DisplayName,State,StartMode,ExitCode,Status FROM Win32_Service WHERE StartMode='Auto' AND State!='Running'" -computername $c -erroraction stop
        } catch {
          Write-Warning "Could not get service list from $($c)"
        }
      }
    }

    # Create the resulting list of services by removing any that are in the ignore list.
    $results = @()
    foreach ($s in $serv) {
      Write-Verbose "Checking if $($s.name) in ignore list."
      if ($ignore -match $s.name) { 
        Write-Verbose "  *Service in ignore list."
      } else {
        Write-Verbose "  Service OK."
        $obj = New-Object -typename PSObject
        $obj | Add-Member -membertype NoteProperty -name ComputerName -value ($s.PSComputerName) -passthru |
               Add-Member -membertype NoteProperty -name ServiceName  -value ($s.Name)           -passthru |
               Add-Member -membertype NoteProperty -name DisplayName  -value ($s.DisplayName)    -passthru |
               Add-Member -membertype NoteProperty -name Status       -value ($s.Status)         -passthru |
               Add-Member -membertype NoteProperty -name State        -value ($s.State)          -passthru |
               Add-Member -membertype NoteProperty -name ExitCode     -value ($s.ExitCode)
        $results += $obj
      }
    }

    # Try and start each of the stopped services that hasn't been ignored.
    if ($StartServices) {
      foreach ($s in $results) {
        Write-Verbose "Starting '$($s.DisplayName)' ($($s.name)) on '$($s.ComputerName)..."
        Try {
          Get-Service -Name $s.name -ComputerName $s.ComputerName -erroraction stop | Start-service -erroraction stop
        } Catch {
          Write-Warning "Could not start service $($s.name) on $($s.ComputerName)."
        }
      }  
    }

    # Output the list of filtered services to the pipeline.
    write-output $results
  }
}
    
por Caynadian 15.09.2015 / 20:57

1 resposta

5

Quando um ou mais objetos se aproximam do host, o PowerShell examina o número de propriedades que o objeto possui.

Se o tipo de um objeto puder ser resolvido para um arquivo Format.ps1xml correspondente (voltaremos a isso em um minuto), a convenção de formatação descrita nesse documento será usada - caso contrário, depende do número de propriedades um objeto tem.

Se um objeto tiver menos de 5 propriedades, o padrão será usar Format-Table para formatação de saída:

PS C:\> New-Object psobject -Property ([ordered]@{PropA=1;PropB=2;PropC=3;PropD=4})

PropA PropB PropC PropD
----- ----- ----- -----
    1     2     3     4

Se um objeto tiver mais propriedades, o padrão será Format-List (que é o que você experimenta):

PS C:\> New-Object psobject -Property ([ordered]@{PropA=1;PropB=2;PropC=3;PropD=4;PropE=5})


PropA : 1
PropB : 2
PropC : 3
PropD : 4
PropE : 5

Agora, a razão pela qual os objetos retornados do cmdlet Get-Service ou Get-Process parecem ser formatados em uma tabela agradável, contextualmente relevante, com mais de 5 colunas, é que o PowerShell conseguiu encontrar um tipo documento de formatação específico para eles.

Esses arquivos de formatação estão todos localizados no diretório de instalação do PowerShell. Você pode localizar os arquivos padrão com:

Get-ChildItem $PSHome *.Format.ps1xml

Veja Get-Help about_Format.ps1xml se você quiser criar seus próprios arquivos de formato.

A maneira como o PowerShell estabelece um link entre o tipo de um objeto e as exibições de formatação definidas é inspecionando a propriedade pstypenames oculta:

PS C:\> $obj.pstypenames
System.Management.Automation.PSCustomObject
System.Object

O PowerShell simplesmente analisa essa lista ancestral de tipos para ver se ela tem uma visualização de formatação correspondente para o tipo.

Isso significa que você pode enganar o PowerShell na formatação de um objeto como se fosse de outro tipo, sem realmente se intrometer no sistema de tipo .NET subjacente.

Para mostrar isso, vamos criar um falso controlador de serviço - um objeto que se parece com algo que Get-Service poderia ter retornado, mas na verdade não é:

PS C:\> $FauxService = New-Object psobject -Property @{
>>>   "Name"        = "FakeService3000"
>>>   "Status"      = "Faking"
>>>   "DisplayName" = "TrustworthyService"
>>>   "TrueName"    = "Really a fake"
>>>   "Author"="Clever Genius"
>>> }
PS C:\> $FauxService


Status      : Faking
Name        : FakeService3000
Author      : Clever Genius
DisplayName : TrustworthyService
TrueName    : Really a fake

Conforme descrito acima, o PowerShell mostra a saída de Format-List , pois nosso psobject tem 5 propriedades.

Agora, vamos tentar injetar um nome de tipo:

PS C:\> $FauxService.pstypenames.Insert(0,"System.ServiceProcess.ServiceController")
PS C:\> $FauxService

Status   Name               DisplayName
------   ----               -----------
Faking   FakeService3000    TrustworthyService

Voila!

    
por 20.09.2015 / 17:23

Tags