Dize Özellikleriyle Daha Eğlenceli


Geçen gün, bir komut satırı aracını çalıştırarak elde edebileceğiniz dize özelliklerini PowerShell adlı bir özelliğe dönüştürme hakkında bir makale yayınladım. Onunla denemeler yapmaya devam ediyordum. İşte bunu nasıl kullanabileceğime dair bazı kodlar.

$raw = qprocess
$properties = $raw[0] -split "\s{2,}" | Convert-StringProperty 
$raw | select -Skip 1 | foreach {
 #split each line
 $data = $_ -split "\s{2,}"
 $hash=[ordered]@{}
 for ($i=0;$i -lt $properties.count;$i++) {
   $hash.Add($properties[$i],$data[$i])
 }
 [pscustomobject]$hash
}

Sonunda şöyle bir çıktı elde ediyorum:

Username    : >jeff
Sessionname : console
Id          : 1
Pid         : 3044
Image       : conhost.exe

Username    : >jeff
Sessionname : console
Id          : 1
Pid         : 4848
Image       : chrome.exe

Değerleri temizlemek ve “<“ gibi word olmayan karakterleri kaldırmak güzel olurdu. Ancak resim özelliğinden noktayı kaldırmak istemem. Böylece şunu buldum:

$raw = qprocess
$properties = $raw[0] -split "\s{2,}" | Convert-StringProperty 
$raw | select -Skip 1 | foreach {
 #split each line
 $data = $_ -split "\s{2,}"
 $hash=[ordered]@{}
 for ($i=0;$i -lt $properties.count;$i++) {
   #remove any < > or # characters from data value
   $hash.Add($properties[$i],($data[$i] -replace "[><#]",""))
 }
 [pscustomobject]$hash
}

Her veri öğesi, normal ifade düzenindeki herhangi bir karakteri hiçbir şeyle değiştirmek için Değiştir operatörü kullanılarak temizlenir. Daha sonra ipconfig /displaydns gibi bir dizi satır olarak görüntülenen komut satırı çıktısını denemeye başladım. Öncelikle neyi işleyeceğime karar vermem gerekiyor, bu yüzden '.' içeren tüm satırları almak istiyorum. . .' içinde.

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}

Çıktıya baktığımda temelde her 5 satırlık grup için bir “nesne” elde ettiğimi görebiliyorum.

$group = 5
for ($i=0;$i -lt $raw.count;$i+=($group+1)) {
   $raw[$i..($i+$group)] | foreach -begin {
  $hash=[ordered]@{}
  } -process {
    #split on the first : into 2 strings
    $split = $_ -split ":",2
    $prop = $split[0] | Convert-StringProperty
    $value = $split[1]
    $hash.add($prop,$value)
  } -end {
     [pscustomobject]$hash
  }
}

Bu kod 5'li gruplar halinde sayılır ve her öğe grubunu bir dizi dizin numarası kullanarak alır. Aralıktaki her satır daha sonra ilk : karakterinde 2 dizeye bölünür. İlk öğe, dize özelliğine dönüştürülen özellik adı, ikinci öğe ise veri olacaktır. Bu bana özel bir nesne veriyor:

RecordName  :  ec2-50-16-209-174.compute-1.amazonaws.com
RecordType  :  1
TimeToLive  :  19610
DataLength  :  4
Section     :  Answer
AhostRecord :  50.16.209.174

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TimeToLive :  86400
DataLength :  8
Section    :  Answer
PtrRecord  :  chi-core01.globomantics.local

Bu da bir sonraki mücadeleye yol açıyor. Son satır, biraz karışık olan iki ayrı özellik olarak yorumlanıyor. Bu yüzden bu satırı yeniden adlandırmanın bir yoluna ihtiyacım var. Veya aslında herhangi bir satır. Belki TimeToLive yerine TTL kullanmak isterim. İşte bir sonraki adım.

$replaceHash=@{5="Record";2="TTL"}

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}
$group = 5

for ($i=0;$i -lt $raw.count;$i+=($group+1)) {
   $raw[$i..($i+$group)] | foreach -begin {
     $hash=[ordered]@{}
     #define an process counter
     $k=0
  } -process {
    $split = $_ -split ":",2
    if ($replaceHash.keys -contains $k) {
      #get replacement property name from hash table
      $prop = $replaceHash.Item($k)
    }
    else {
        #convert existing property name
        $prop = $split[0] | Convert-StringProperty
    }
    $value = $split[1]
    $hash.add($prop,$value)
    #increment the property counter
    $k++
  } -end {
     [pscustomobject]$hash
  }
}

Her mülkün hangi satır numarasında olacağını önceden bilmem gerekiyor. Ancak bunu yaptığımdan beri, satır numarasını (0'dan başlayarak) anahtar olarak kullanan bir karma tablosu oluşturacağım. Hashtable değeri, değiştirilen özelliğin adı olacaktır. Süreç komut dosyası bloğumda kaç satır üzerinde çalıştığımı takip ediyorum. Sayaç karma tablosundaki bir anahtarla eşleşiyorsa, değiştirme işlemini gerçekleştiririm. Şimdi şunu anlıyorum:

RecordName :  ec2-50-16-209-174.compute-1.amazonaws.com
RecordType :  1
TTL        :  19044
DataLength :  4
Section    :  Answer
Record     :  50.16.209.174

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TTL        :  86400
DataLength :  8
Section    :  Answer
Record     :  chi-core01.globomantics.local

Bu noktada bunu bir fonksiyona dönüştürebileceğim başka bir tekrarlanabilir kodum olduğunu fark ettim.

Function Convert-CLIOutput {

<#
.SYNOPSIS
Convert CLI-based tool output to PowerShell object
.DESCRIPTION
This command will process output from a command line tool and turn it into
PowerShell pipeline output. It relies on the Convert-StringProperty function.
This command works best with tabular output where column headings are separated
by at least 2 spaces.

You can use list oriented data but you must tell the command you are processing
a list as well as tell it how many lines are grouped together to form a new
object.

By default, the command will remove any non-word characters from the data such as
> or # using a regular expression pattern for -Cleanup. Use -NoCleanup if you want
the data as it appears in the command line tool output. NoCleanup will take
precedence over Cleanup.

Sometimes properties aren't what you want so you can use a custom hashtable
to specify replacement values. The key will the number of the property starting
at 1. In a table count left to right. In a list count top down. The value is 
the replacement property value.

You cannot pipe anything into this command.
.PARAMETER Delimiter
The delimiter for property names in the CLI output. The default is a space.
This command uses Convert-StringProperty to convert a string like 
"Internet address" into a "InternetAddress"
.PARAMETER ReplaceHash
A hashtable of replacement property values.  The key will the number of the 
property starting at 1. In a table count left to right. In a list count top 
down. The value is the replacement property value. See examples.
.PARAMETER List
Specify that the input is formatted as a list
.PARAMETER Group
The number of lines that will be grouped together to define a single set of 
.PARAMETER LineDelimiter
When processing line output, this will specify the delimiter separating the
property from the value. The default is the colon.
object properties.
.PARAMETER Cleanup
A regular expression pattern of characters to clean up from data output. The
default is [<>$#].
.EXAMPLE
PS C:\> $raw = netstat -n | select -skip 3
PS C:\> convert-clioutput $raw | where {$_.state -match "Time_Wait"}

Proto              LocalAddress                     ForeignAddress                   State                           
-----              ------------                     --------------                   -----                           
TCP                172.16.10.127:50115              172.16.10.86:80                 TIME_WAIT 

.EXAMPLE
PS C:\> $raw = ipconfig /displaydns | where {$_ -match "\.\s"}
PS C:\> Convert-CLIOutput $raw -list -group 6 -ReplaceHash @{6="Record"} -NoCleanup

RecordName :  ec2-54-221-234-232.compute-1.amazonaws.com
RecordType :  1
TimeToLive :  1277
DataLength :  4
Section    :  Answer
Record     :  54.221.234.232

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TimeToLive :  86400
DataLength :  8
Section    :  Answer
Record     :  chi-core01.globomantics.local
...

The output from Ipconfig /displaydns is presented in a list. In this example, the 
output for each entry is grouped in 6 lines. The replacement hash table replaces
the property for the 6th line with a different value. The expression is using the
-NoCleanup parameter so that record names are not stripped of periods and dashes.
.EXAMPLE
PS C:\> Convert-CLIOutput (quser) -ReplaceHash @{1="User";2="Session"} 

User      : jeff
Session   : console
Id        : 1
State     : Active
IdleTime  : none
LogonTime : 3262014345PM

This command takes table output from quser.exe and turns it into an object using
a hashtable of replacement values.
#>

[cmdletbinding(DefaultParameterSetName="Table")]
Param(
[Parameter(Position=0)]
$InputObject,
[string]$Delimiter=" ",
[switch]$NoCleanup,
[hashtable]$ReplaceHash,
[regex]$Cleanup="[<>#?]",
[Parameter(ParameterSetName="List")]
[switch]$List,
[Parameter(ParameterSetName="List",Mandatory=$True,
HelpMessage="How many lines in a group?")]
[int]$Group,
[Parameter(ParameterSetName="List")]
[ValidateNotNullorEmpty()]
[string]$LineDelimiter=":"
)

Begin {
    Write-Verbose "Starting $($MyInvocation.Mycommand)"
    Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)"
    [regex]$rx="\s{2,}"
} #Begin
Process {

    If ($PSCmdlet.ParameterSetName -eq "Table") {

        Write-Verbose "Processing property names"
        $properties = $rx.Split($InputObject[0].trim()) | Convert-StringProperty -Delimiter $delimiter

        Write-Verbose "Converting data to objects"
        for ($i=1;$i -lt $inputObject.count; $i++) {
          $splitData = $rx.split($inputobject[$i].Trim())
          #create an object for each entry
          $hash = [ordered]@{}
          for ($j=0;$j -lt $properties.count;$j++) {
             if ($NoCleanup) {
                 $value = $splitData[$j]
             }
             else {
                #add each data element stripped of any non word characters like < or #
                $value = $splitData[$j] -replace $Cleanup,''
            }

            #replace properties with values from hashtable
            if ($ReplaceHash.keys -contains ($j+1)) {

               $newProperty = $ReplaceHash.Item($j+1)
               Write-verbose "Using replacement property $newproperty"
            }
            else {
               $newProperty = $properties[$j]
            }
            #add the property key and value to the hashtable
            $hash.Add($newProperty,$value)
          } #for
            #Write a custom object to the pipeline
            [pscustomobject]$hash
        } #for
    } #if table
    else {
      for ($i=0;$i -lt $inputobject.count;$i+=$group) {
       $inputobject[$i..($i+($group-1))] | foreach -begin {
         $hash=[ordered]@{}
         #define an process counter
         $k=1
      } -process {
        #split the line on the first line delimiter.
        $split = $_ -split $LineDelimiter,2
        if ($replaceHash.keys -contains $k) {
          #get replacement property name from hash table if processing the 
          #matching line number
          $prop = $replaceHash.Item($k)
        }
        else {
            #convert existing property name
            $prop = $split[0] | Convert-StringProperty -Delimiter $delimiter
        }
        if ($NoCleanup) {
            $value = $split[1]
        }
        else {
              #add each data element stripped of any non word characters like < or #
              $value = $split[1] -replace $Cleanup,''
         }

        $hash.add($prop,$value)
        #increment the property counter
        $k++
       } #process
        #Write a custom object to the pipeline
        [pscustomobject]$hash
    } #for
    } #else   

} #process

End { 
    Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
}
} #end function

Bu işlev Convert-StringProperty'ye dayanır. Bir noktada bunu ve sahip olduğum diğer birkaç şeyi bir modülde paketlemeliyim. Ancak şimdilik diğer işlevin kabuğunuza yüklendiğinden emin olmanız gerekecek. Varsayılan işleme, arp -a'dan aldığınız gibi tablo şeklinde çıktıya sahip olduğunuzu varsayar. Bir listedeki girişi belirtebilirsiniz ancak daha sonra kaç satırın birlikte gruplandırılacağını da belirtmeniz gerekir. Ayrıca her iki giriş türü için de değiştirilen özellik adlarının karma tablosunu belirtebilirsiniz. İşleri kolaylaştırmak için hash tablosu anahtarları 1'den saymaya başlar.

Komuta giremezsiniz. Girişi şu şekilde belirtmeniz gerekir:

Özelliğin yeniden adlandırılması da dahil olmak üzere tablo çıktısının kullanıldığı diğer bazı örnekleri burada bulabilirsiniz.

Daha önceki IPCONFIG örneğimi kullanarak, bunu yeni fonksiyonumla yapabilirim.

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}
Convert-CLIOutput $raw -ReplaceHash @{3="TTL";6="Record"} -list -group 6 | Out-GridView -Title DisplayDNS

Izgara görünümünde daha fazla filtreleme veya sıralama yapabilirim.

Burada kalan bir sorun, her şeyin bir dize olmasıdır. Ancak bu bakımdan, bir CSV dosyasını içe aktardığınızdan pek de farklı değil. Sanırım bu bana üzerinde çalışacak başka bir şey veriyor. Bu arada, bunu deneyin ve ne düşündüğünüzü bana bildirin.

İlgili makaleler