I have a hashtable of file extensions with counts
like so:
$FileExtensions = #{".foo"=4;".bar"=5}
Function HashConvertTo-String($ht) {
foreach($pair in $ht.GetEnumerator()) {
$output+=$pair.key + "=" + $pair.Value + ";"
}
$output
}
$hashString = HashConvertTo-String($FileExtensions)
$hashString.TrimEnd(';') -eq ".foo=4;.bar=5"
The last line should return $true
This works but looking for a more elegant way (removing trailing ; is optional)
I guess what I'm really looking for is a -join for hashtables or something similar
Thoughts???
PowerShell won't automatically enumerate a hashtable, so you're forced to call GetEnumerator() or the Keys property. After that, there are a few options. First, using $OFS Ouptut Field Seperator. This string is used when an array is converted to a string. By default, this is "" but you can change it:
$FileExtensions = #{".foo"=4;".bar"=5}
$OFS =';'
[string]($FileExtensions.GetEnumerator() | % { "$($_.Key)=$($_.Value)" })
Next using the -join operator:
$FileExtensions = #{".foo"=4;".bar"=5}
($FileExtensions.GetEnumerator() | % { "$($_.Key)=$($_.Value)" }) -join ';'
Not tested but this code should work:
Function HashConvertTo-String($ht) {
$first = $true
foreach($pair in $ht.GetEnumerator()) {
if ($first)
{
$first = $false
}
else
{
$output += ';'
}
$output+="{0}={1}" -f $($pair.key),$($pair.Value)
}
$output
}
Related
Is it possible to create an anonymous Recursive Function in PowerShell? (if yes, how?)
I have a recursive object and using a recursive function to drill down through the properties, like:
$Object = ConvertFrom-Json '
{
"Name" : "Level1",
"Folder" : {
"Name" : "Level2",
"Folder" : {
Name : "Level3"
}
}
}'
Function GetPath($Object) {
$Object.Name
if ($Object.Folder) { GetPath $Object.Folder }
}
(GetPath($Object)) -Join '\'
Level1\Level2\Level3
The function is relative small and only required ones, therefore I would like to directly invoke it as an anonymous function, some like:
(&{
$Object.Name
if ($Object.Folder) { ???? $Object.Folder }
}) -Join '\'
Is this possible in PowerShell?
If yes, how can I (as clean as possible) refer to the current function at ?????
Unfortunately there is not much documentation on this topic but you could execute the anonymous script block via $MyInvocation automatic variable, specifically it's ScriptInfo.ScriptBlock Property.
A simple example:
& {
param([int] $i)
if($i -eq 10) { return $i }
($i++)
& $MyInvocation.MyCommand.ScriptBlock $i
}
# Results in 0..10
Using your current code and Json provided in question:
(& {
param($s)
$s.Name; if ($s.Folder) { & $MyInvocation.MyCommand.ScriptBlock $s.Folder }
} $Object) -join '\'
# Results in Level1\Level2\Level3
Same as the above but using pipeline processing instead:
($Object | & {
process {
$_.Name; if($_.Folder) { $_.Folder | & $MyInvocation.MyCommand.ScriptBlock }
}
}) -join '\'
A bit more code but the same can be accomplished using a Collections.Queue instead of recursion, which is likely to be more resource efficient:
$(
$queue = [System.Collections.Queue]::new()
$queue.Enqueue($object)
while($queue.Count) {
$node = $queue.Dequeue()
$node.Name
if($node.Folder) { $queue.Enqueue($node.Folder) }
}
) -join '\'
#Santiago's helpful answer was exactly where I was initially looking for.
Nevertheless, it doesn't always require a recursive function to crawl through a recursive object.
As in the mcve, I could just have done:
#(
do {
$Object.Name
$Object = $Object.Folder
} while ($Object)
) -Join '\'
I have this:
Get-ADUser myuser |
Select #{n='OU';e={$_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)'}}
But I need to get only on part of the OU of a specific user which I have to define as a variable in the beginning.
I get this
OU=Users,OU=Munich,DC=xyzdom,DC=xyz
And I want to detect if the user is in the Munich OU or where ever.
So the output should be just $city and the input $username
I have no clue how to do this. But I suspect it should be not as hard to achieve this goal.
Maybe someone has time and passion to show me how :)
Thank you so much
Greetings
Thanks a lot for the help. (I can't use the city property.) My solution looks like this now:
Import-Module ActiveDirectory
$samaccountname = "Smith"
$ou = Get-ADUser $samaccountname | Select #{n='OU';e={$_.DistinguishedName.split(',')[-3].split("=")[-1]}} | FT -HideTableHeaders
$ou
Now, the output is just: Munich
I want to go on using this variable but maybe it's in a wrong format. when I try to use it with orchestrator I get an output like this: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
So maybe it has to be formated as string??? How can I do that?
I agree with Santiago that using the users AD attribute City would be a much better solution, but if you don't have that filled in on the users, you may try below.
A DistinguishedName can contain commas, escaped characters and even special characters converted to their HEX representation.
See here and there
Simply splitting a DN on the comma can therefore return unwanted results.
For this, I've written a small helper function some time ago you could use:
function Parse-DistinghuishedName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string[]]$DistinghuishedName
)
begin {
function _ReplaceSpecial([string]$value) {
# replace all special characters formatted as BackSlash-TwoDigitHexCode
$match = ([regex]'(?i)\\([0-9a-f]{2})').Match($value)
while ($match.Success) {
$value = $value -replace "\\$($match.Groups[1].Value)", [char][convert]::ToUInt16($match.Groups[1].Value, 16)
$match = $match.NextMatch()
}
# finally, replace all backslash escaped characters
$value -replace '\\(.)', '$1'
}
}
process {
foreach ($dn in $DistinghuishedName) {
$hash = [ordered]#{}
# split the string into separate RDN (RelativeDistinguishedName) components
$dn -split ',\s*(?<!\\,\s*)' | ForEach-Object {
$name, $value = ($_ -split '=', 2).Trim()
if (![string]::IsNullOrWhiteSpace($value)) {
$value = _ReplaceSpecial $value
switch ($name) {
'O' { $hash['Organization'] = $value }
'L' { $hash['City'] = $value }
'S' { $hash['State'] = $value }
'C' { $hash['Country'] = $value }
'ST' { $hash['StateOrProvince'] = $value }
'UID' { $hash['UserId'] = $value }
'STREET' { $hash['Street'] = $value }
# these RDN's can occur multiple times, so add as arrays
'CN' { $hash['Name'] += #($value) }
'OU' { $hash['OrganizationalUnit'] += #($value) }
'DC' { $hash['DomainComponent'] += #($value) }
}
}
}
$hash
}
}
}
It parses the DN into its RDN components and returns a Hashtable.
In your case, use it like:
(Parse-DistinghuishedName 'OU=Users,OU=Munich,DC=xyzdom,DC=xyz').OrganizationalUnit[1] # --> Munich
I received a config file of a F5 loadbalancer and was asked to parse it with PowerShell so that it creates a .txt file for every iRule it finds. I'm very new to parsing and I can't seem to wrap my head around it.
I managed to extract the name of every rule and create a separate .txt file, but I am unable to wring the content of the rule to it. Since not all rules are identical, I can't seem to use Regex.
Extract from config file:
ltm rule /Common/irule_name1 {
SOME CONTENT
}
ltm rule /Common/irule_name2 {
SOME OTHER CONTENT
}
What I have for now
$infile = "F5\config_F5"
$ruleslist = Get-Content $infile
foreach($cursor in $ruleslist)
{
if($cursor -like "*ltm rule /*") #new object started
{
#reset all variables to be sure
$content=""
#get rulenames
$rulenameString = $cursor.SubString(17)
$rulename = $rulenameString.Substring(0, $rulenameString.Length -2)
$outfile = $rulename + ".irule"
Write-Host $outfile
Write-Host "END Rule"
#$content | Out-File -FilePath "F5/irules/" + $outfile
}
}
How can I make my powershell script read out what's between the brackets of each rule? (In this case "SOME CONTENT" & "SOME OTHER CONTENT")
Generally parsing involves converting a specific input ("string") into an "object" which PowerShell can understand (such as HTML, JSON, XML, etc.) and traverse by "dotting" through each object.
If you are unable to convert it into any known formats (I am unfamiliar with F5 config files...), and need to only find out the content between braces, you can use the below code.
Please note, this code should only be used if you are unable to find any other alternative, because this should only work when the source file used is code-correct which might not give you the expected output otherwise.
# You can Get-Content FileName as well.
$string = #'
ltm rule /Common/irule_name1 {
SOME CONTENT
}
ltm rule /Common/irule_name2 {
SOME OTHER CONTENT
}
'#
function fcn-get-content {
Param (
[ Parameter( Mandatory = $true ) ]
$START,
[ Parameter( Mandatory = $true ) ]
$END,
[ Parameter( Mandatory = $true ) ]
$STRING
)
$found_content = $string[ ( $START + 1 ) .. ( $END - 1 ) ]
$complete_content = $found_content -join ""
return $complete_content
}
for( $i = 0; $i -lt $string.Length; $i++ ) {
# Find opening brace
if( $string[ $i ] -eq '{' ) {
$start = $i
}
# Find ending brace
elseif( $string[ $i ] -eq '}' ) {
$end = $i
fcn-get-content -START $start -END $end -STRING $string
}
}
For getting everything encompassed within braces (even nested braces):
$string | Select-String '[^{\}]+(?=})' -AllMatches | % { $_.Matches } | % { $_.Value }
To parse data with flexible structure, one can use a state machine. That is, read data line by line and save the state in which you are. Is it a start of a rule? Actual rule? End of rule? By knowing the current state, one can perform actions to the data. Like so,
# Sample data
$data = #()
$data += "ltm rule /Common/irule_name1 {"
$data += "SOME CONTENT"
$data += "}"
$data += "ltm rule /Common/irule_withLongName2 {"
$data += "SOME OTHER CONTENT"
$data += "SOME OTHER CONTENT2"
$data += "}"
$data += ""
$data += "ltm rule /Common/irule_name3 {"
$data += "SOME DIFFERENT CONTENT"
$data += "{"
$data += "WELL,"
$data += "THIS ESCALATED QUICKLY"
$data += "}"
$data += "}"
# enum is used for state tracking
enum rulestate {
start
stop
content
}
# hashtable for results
$ht = #{}
# counter for nested rules
$nestedItems = 0
# Loop through data
foreach($l in $data){
# skip empty lines
if([string]::isNullOrEmpty($l)){ continue }
# Pick the right state and keep count of nested constructs
if($l -match "^ltm rule (/.+)\{") {
# Start new rule
$state = [rulestate]::start
} else {
# Process rule contents
if($l -match "^\s*\{") {
# nested construct found
$state = [rulestate]::content
++$nestedItems
} elseif ($l -match "^\s*\}") {
# closing bracket. Is it
# a) closing nested
if($nestedItems -gt 0) {
$state = [rulestate]::content
--$nestedItems
} else {
# b) closing rule
$state = [rulestate]::stop
}
} else {
# ordinary rule data
$state = [rulestate]::content
}
}
# Handle rule contents based on state
switch($state){
start {
$currentRule = $matches[1].trim()
$ruledata = #()
break
}
content {
$ruledata += $l
break
}
stop {
$ht.add($currentRule, $ruledata)
break
}
default { write-host "oops! $state" }
}
write-host "$state => $l"
}
$ht
Output rules
SOME CONTENT
SOME OTHER CONTENT
SOME OTHER CONTENT2
SOME DIFFERENT CONTENT
{
WELL,
THIS ESCALATED QUICKLY
}
I'm trying add to a variable and a string in an array dynamically but i'm not getting expected output.
(1) I'm getting env name
(2) Concatinating the string and variable in an array
Code is as follows.
$env = $env:COMPUTERNAME.Substring(0,2)
$servers = { $env+"server1.test.com",$env+"server2.test.com" }
$serverCount = $servers -split(",") | measure | % { $_.Count }
For ($i=0; $i -lt $serverCount; $i++)
{
$ServerName = $servers -split(',') -replace '\[\d+\]'
$server = $ServerName[$i]
Write-Host $server
}
output i'm getting as
$env+"server1.test.com"
$env+"server2.test.com"
Values are not getting concatenated properly and variable value is not getting displayed. Any help.
$servers = { $env+"server1.test.com",$env+"server2.test.com" }
This is a scriptblock, not an array. {} is like a function, you have to run it for it to do anything (such as evaluating $env).
When you force it into a string using -split(",") what you get is text representation of the source code in the scriptblock, including the variable names.
As #Olaf comments, the right way to create an array of names is
$servers = ($env + "server1.test.com"), ($env + "server2.test.com")
This might be how I'd write it:
$env = $env:COMPUTERNAME.Substring(0,2)
"server1.test.com", "server2.test.com" | foreach-object {
"$env$_" -replace '\d+'
}
I want to create a powershell script that accepts dynamic parameters and I also want to iterate through them.
eg:
I call the powershell script in the following manner.
ParametersTest.ps1 -param1 value1 -param2 value2 -param3 value3
And I should be able to access my params inside the script as follows:
for($key in DynamicParams) {
$paramValue = DynamicParams[$key];
}
Is there anyway to do this in powershell? Thanks in advance.
There is nothing built-in like that (essentially you're asking for PowerShell parameter parsing in the absence of any definition of those parameters). You can emulate it, though. With $args you can get at all arguments of the function as an array. You can then iterate that and decompose it into names and values:
$DynamicParams = #{}
switch -Regex ($args) {
'^-' {
# Parameter name
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
$name = $_ -replace '^-'
}
'^[^-]' {
# Value
$value = $_
}
}
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
To iterate over dynamic parameters you can either do something like you wrote
foreach ($key in $DynamicParams.Keys) {
$value = $DynamicParams[$key]
}
(note the foreach, not for, the latter of which cannot work like you wrote it) or just iterate normally over the hash table:
$DynamicParams.GetEnumerator() | ForEach-Object {
$name = $_.Key
$value = $_.Value
}