How to read in key-value pairs as a template for String processing with PowerShell? - string

Very concisely writing a here string to file:
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> #'
>> foo : {abc}
>> bar : {123}
>> baz : {a1b2c3}
>> '# | Out-File here_string_output.txt
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> cat .\here_string.txt
#'
foo : {abc}
bar : {123}
baz : {a1b2c3}
'#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $hereString = #'
>> foo : {abc}
>> bar : {123}
>> baz : {a1b2c3}
>> '#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $hereString
foo : {abc}
bar : {123}
baz : {a1b2c3}
PS C:\Users\saunders\Desktop\misc>
How would the inverse, reading the contents of here_string.txt into the $hereString variable?
------------------------original form of question--------------------
Working with sample data:
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $recordTemplate = #'
>> UserPrincipalName : {UserPrincipalName*:LeeG#lazydev.com}
>> DisplayName : {DisplayName:Lee Gu}
>> Title : {Title:jr engineer}
>> UserType : {UserType:Member}
>> IsLicensed : {IsLicensed:True}
>>
>> UserPrincipalName : {UserPrincipalName*:MeganB#lazydev.com}
>> '#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $recordTemplate
UserPrincipalName : {UserPrincipalName*:LeeG#lazydev.com}
DisplayName : {DisplayName:Lee Gu}
Title : {Title:jr engineer}
UserType : {UserType:Member}
IsLicensed : {IsLicensed:True}
UserPrincipalName : {UserPrincipalName*:MeganB#lazydev.com}
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> cat .\recordTemplate.txt
$recordTemplate = #'
UserPrincipalName : {UserPrincipalName*:LeeG#lazydev.com}
DisplayName : {DisplayName:Lee Gu}
Title : {Title:jr engineer}
UserType : {UserType:Member}
IsLicensed : {IsLicensed:True}
UserPrincipalName : {UserPrincipalName*:MeganB#lazydev.com}
'#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $newRecordTemplate = Get-Content .\recordTemplate.txt
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $newRecordTemplate
$recordTemplate = #'
UserPrincipalName : {UserPrincipalName*:LeeG#lazydev.com}
DisplayName : {DisplayName:Lee Gu}
Title : {Title:jr engineer}
UserType : {UserType:Member}
IsLicensed : {IsLicensed:True}
UserPrincipalName : {UserPrincipalName*:MeganB#lazydev.com}
'#
PS C:\Users\saunders\Desktop\misc>
How would the above template recordTemplate.txt be read as a text file for processing as a template with either ConvertFrom-String or ConvertFrom-StringData?
So that the template itself can be read from a file rather than typed directly into the interactive CLI.
The file being parsed is records.txt with the first two records being:
UserPrincipalName : LeeG#lazydev.com
DisplayName : Lee Gu
Title : jr engineer
UserType : Member
IsLicensed : True
UserPrincipalName : MeganB#lazydev.com
DisplayName : Megan Bowen
Title : recruiter
UserType : Member
IsLicensed : True
Yes, that's string data, and, yes, looking to utilize ConvertFrom-String or ConvertFrom-StringData but first need to read a template from file. The template is, I think, a here string.

Related

How to parse a here-string in PowerShell as a hash?

This example is easy enough:
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $hash = #"
>> Name = Jackson
>> Employer = BigData
>> EmpID = 2032
>> Type = Permanent
>> "#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $hash
Name = Jackson
Employer = BigData
EmpID = 2032
Type = Permanent
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $hash | ConvertFrom-StringData
Name Value
---- -----
Employer BigData
Type Permanent
EmpID 2032
Name Jackson
PS C:\Users\saunders\Desktop\misc>
Which is excellent and the desired output. Here:
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> cat .\hash.txt
$hash = #"
Name = Jackson
Employer = BigData
EmpID = 2032
Type = Permanent
"#
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $foo = Get-Content .\hash.txt
PS C:\Users\saunders\Desktop\misc>
PS C:\Users\saunders\Desktop\misc> $foo
$hash = #"
Name = Jackson
Employer = BigData
EmpID = 2032
Type = Permanent
"#
PS C:\Users\saunders\Desktop\misc>
the data originates in a file. How is this file read as a here-string so that it can be used as above?
Your .\hash.txt is in effect a .ps1 script file, so you could change its extension an then load it via ., the dot-sourcing operator, at which point $hash will be defined and can be passed to ConvertFrom-StringData.
To process the file as-is, you can pass its content to Invoke-Expression (though note that this cmdlet should generally be avoided):
Invoke-Expression (Get-Content -Raw t.txt)
$hash | ConvertFrom-StringData
If you have control over how .\hash.txt gets created and can change its name, you can save it as a PowerShell data file, i.e. with extension .psd1, which can be read as-is with the Import-PowerShellDataFile cmdlet:
A .psd1 file is in effect the source-code representation of a hashtable literal, whose syntax requirements are more stringent than for text that can be parsed with ConvertFrom-StringData:
# Save the source-code representation of a hashtable literal
# to a file - do NOT include a variable assignment:
#'
#{
Name = 'Jackson'
Employer = 'BigData'
EmpID = 2032
Type = 'Permanent'
}
'# > hash.psd1
Import-PowerShellDataFile hash.psd1
As an aside:
As of PowerShell 7.3.2, both ConvertFrom-StringData and Import-PowerShellData do not preserve the entries in definition order, given that [hasthable] instances are inherently unordered.
GitHub issue #19070 proposes overcoming this limitation, by making these cmdlets return ordered hashtables instead, via System.Management.Automation.OrderedHashtable, which derives from [hashtable].

Using select-string to output list from 1000+ files

I'm trying to get a specific string from a text file and output it and the filename to a separate txt file. I have tried the following but get an error message. I've looked for answers but haven't found any. Any help is appreciated.
I Should add that I'm fairly new to Powershell.
Select-String -Path C:\temp\test1.txt -Pattern 'batch3'|ForEach-Object {
#' File name - {0} {1}
..................... '# -f $_.Name, (Get-Content $_.FullName -Raw) }
| Out-File 'C:\temp\test_output.txt'
It works if I substitute Select-String for Get-Content. The problem then is that it takes the entire content of the file and that is not what I need.
error message:
Get-Content : Cannot bind argument to parameter 'Path' because it is null.
At line:6 char:29
+ '# -f $.Name, (Get-Content $.FullName -Raw)
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetContentCommand
Select-String does not output a property FullName. However, there is Path property. Try this:
(Get-Content $_.Path -Raw)
This will fix the error, but if you want to output just a line with the string you found, not the entire file contents, remove Get-Content and try this:
Select-String -Path C:\temp\test1.txt -Pattern 'batch3'|ForEach-Object {
#' File name - {0} {1}
..................... '# -f $_.Filename, $_.Line }
| Out-File 'C:\temp\test_output.txt'

loop through azure objects and filter them on tags

I have a question , recently we started using Tagging in Azure and needed to list specific object by thier Tags. Below is the script used to find the resources. in both cases we find a specific object in the results when using match and notmatch. This is strange behaviour when searching Tagged resources. what other ways do you use to complete the task?
PS C:\WINDOWS\system32> $KeyName = 'Department'
PS C:\WINDOWS\system32> $NewKeyValue = "PROD, Data"
PS C:\WINDOWS\system32> $AzSqlServer = Get-AzSqlServer
PS C:\WINDOWS\system32> if($AzSqlServer)
>> {
>> foreach ($server in $AzSqlServer )
>> {
>> $SQLDatabase = Get-AzSqlDatabase -ServerName $server.ServerName -ResourceGroupName $server.ResourceGroupName| Where-Object {$_.tags.Values -notmatch "PROD, Data"}
>> write-output $SQLDatabase.DatabaseName
>> }
>> }
DBname1
DBname2
DBname3
DBname4
master
master
master
**DBname5**
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $KeyName = 'Department'
PS C:\WINDOWS\system32> $NewKeyValue = "PROD, Data"
PS C:\WINDOWS\system32> $AzSqlServer = Get-AzSqlServer
PS C:\WINDOWS\system32> if($AzSqlServer)
>> {
>> foreach ($server in $AzSqlServer )
>> {
>> $SQLDatabase = Get-AzSqlDatabase -ServerName $server.ServerName -ResourceGroupName $server.ResourceGroupName| Where-Object {$_.tags.Values -match "PROD, Data"}
>> write-output $SQLDatabase.DatabaseName
>> }
>> }
**DBname5**
The problem here is with unexpected results of using -notmatch and -match operators against a list. Switching to -notcontains and -contains for exact matches will produce the desired result.
$AzSqlServer = Get-AzSqlServer
if ($AzSqlServer) {
foreach ($server in $AzSqlServer) {
$SQLDatabase = Get-AzSqlDatabase -ServerName $server.ServerName -ResourceGroupName $server.ResourceGroupName |
Where-Object {$_.tags.Values -notcontains "PROD, Data"}
$SQLDatabase.Databasename
}
}
When using -notcontains, a list's items are compared in their entirety. The list will only return true if there are no items that match the target item. When using -notmatch against a list, the list's items that do not match the target item are returned. Any returned item results in a True result in a boolean statement. Below is a simplified example of your experience:
# Notice how both boolean statements return True
PS> 1,2,3 -notmatch 2
1
3
PS> [bool](1,2,3 -notmatch 2)
True
PS> 1,2,3 -match 2
2
PS> [bool](1,2,3 -match 2)
True
# Now using -notcontains and -contains
PS> 1,2,3 -notcontains 2
False
PS> 1,2,3 -contains 2
True

Fetching Log Analytics Primary Key

I was trying to get the Azure Log Analytics Workspace primary key by using this command:
Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName "abc-rg" -Name "abc-LAW"
The above command outputs both primary & secondary keys. But how can I just get the primary key to a variable and use it in my PowerShell code?
I was trying this line to get the FIRST key:
(Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName aby-rg -Name abys-LAW).value[0]
but I get an error:
Cannot index into a null array.
At line:36 char:1
[string]$omsSharedKey = (Get-AzOperationalInsightsWorkspaceSharedKey ...
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
Thanks,
The output of the Get-AzOperationalInsightsWorkspaceSharedKey cmdlet is an object of type PSWorkspaceKeys, with two properties:
PrimarySharedKey
SecondarySharedKey
So you should be able to get to the PrimarySharedKey with $keys.PrimarySharedKey.
When in doubt, you can use the GetType method on the object to know its type and Get-Member cmdlet to view the complete listing of its members.
PS C:\> $keys = Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName <ResourceGroup> -Name <WorkspaceName>
PS C:\> $keys
PrimarySharedKey : 5LLi6guNfYKJ0ipSY9cv0NJ8ZeOAHlz7dX0hI9S5RTI6ISnGwa3aTvq/Mzg2SlwaaRqkZAAaSiYStlpx48y8zQ==
SecondarySharedKey : YAQo85BdaEc+W7GD2BCmXbvMFtS9XkMYui6pECl6dZS9rqk7gZjy5wo7s0/Tk+Ceq9zaNW9bbggSUaYzpeUrXw==
PS C:\> $keys.PrimarySharedKey
5LLi6guNfYKJ0ipSY9cv0NJ8ZeOAHlz7dX0hI9S5RTI6ISnGwa3aTvq/Mzg2SlwaaRqkZAAaSiYStlpx48y8zQ==
PS C:\> $keys.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSWorkspaceKeys System.Object
PS C:\> $keys | Get-Member -MemberType Properties
TypeName: Microsoft.Azure.Commands.OperationalInsights.Models.PSWorkspaceKeys
Name MemberType Definition
---- ---------- ----------
PrimarySharedKey Property string PrimarySharedKey {get;set;}
SecondarySharedKey Property string SecondarySharedKey {get;set;}
Reference: Get-AzOperationalInsightsWorkspaceSharedKey
Fetching Log Analytics workspace primary key using azure cli
az monitor log-analytics workspace get-shared-keys --resource-group $ResourceGroup --workspace-name $loganalyticsname --query "primarySharedKey"
Reference Azure Documentation

Trouble parsing string to object with PowerShell

I have a string with structured data (see below). I need to take this string and convert it to an object, so I can export it to .csv (or whatever else is requested of me). I ran the following code:
$data = $string -replace "\s*:\s*","="
But my output looks like this:
City=Country=Department=DisplayName=John Doe
DistinguishedName=CN=John Doe, CN=Users, DC=domain, DC=com
EmailAddress=jdoe#domain.com
Enabled=False
Fax=GivenName=John
MobilePhone=Name=John Doe
ObjectClass=user
ObjectGUID=cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office=OfficePhone=PostalCode=SamAccountName=jdoe
SID=S-1-5-21-2025429266-2000478354-1606980848-16934
State=StreetAddress=Surname=Doe
Title=UserPrincipalName=jdoe#domain.com
This is clearly not correct. What is a better way to make this conversion? I thought about using ConvertFrom-String with the TemplateContent parameter, but haven't been able to make that work yet.
Here are the first two entries in the string (which contains several users worth of data):
$string = #"
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
City :
Country :
Department :
DisplayName : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}
DistinguishedName : CN=DiscoverySearchMailbox {D919BA15-46A6-415f-80AD-7E09334BB852},CN=Users,DC=domain,DC=com
EmailAddress : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}#domain.com
Enabled : False
Fax :
GivenName :
MobilePhone :
Name : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}
ObjectClass : user
ObjectGUID : 0f35137a-de93-472f-9114-5488a462d178
Office :
OfficePhone :
PostalCode :
SamAccountName : SM_2187102a90634829b
SID : S-1-5-21-2438891277-1009865731-3229889747-3109
State :
StreetAddress :
Surname : MsExchDiscoveryMailbox D919BA15-46A6-415f-80AD-7E09334BB852
Title :
UserPrincipalName : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}#domain.com
"#
Thanks.
If:
you can rely on values never containing : themselves
you don't mind that the properties of the resulting custom objects don't reflect the input order (though you could easily, but inefficiently, correct that with piping to a Select-Object call enumerating the properties explicitly),
you can use ConvertFrom-StringData (I suggest avoiding the finicky and poorly documented ConvertFrom-String):
$string.Trim() -split '(?m)(?=^City\b)' -ne '' | ForEach-Object {
[pscustomobject] ($_ -replace ':', '=' | ConvertFrom-StringData)
} # | Export-Csv ....
Note: Casting to [pscustomobject] requires PSv3+; on PSv2, use New-Object PSCustomObject -Property (...)
$string.Trim() -split '(?m)(?=^City\b)' -ne '' splits the input lines into blocks of lines each representing one object; splitting is performed by lines that start with City; -ne '' filters out the empty block that results from parsing the start of the input.
.Trim() is needed to ignore empty lines at the start of the string.
$_ -replace ':', '=' | ConvertFrom-StringData converts each block into
<key>=<value> lines that ConvertFrom-StringData converts as a group to a [hashtable] instance; because hash tables inherently enumerate their entries in no guaranteed order, this is where the input ordering of properties is lost.
Cast [pscustomobject] converts each hashtable to a custom object, which is implicitly output; the output can be piped to Export-Csv.
Here You go:)
$a=#"
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
"#
$b=ConvertFrom-Csv -InputObject $a -Delimiter ':' -Header "key","value"
$c=New-Object -TypeName System.Management.Automation.PSObject
$b|%{ $c|Add-Member -NotePropertyName $_.key -NotePropertyValue "$($_.value)"}
Resulting object looks like this
PS C:\Users\Tomasz> $c|gm
S C:\Users\Tomasz> $c
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
If this kind of solution seems like a good Idea I'll work on my answer more.
It obviously needs white spaces removal and some nicer variable names, but I trust You can get that done Yourself :)
The escape sequence \s matches all whitespace, including newlines. Because of that lines without a value are actually merged with the next line. Split the string at newlines, do the replacement, then merge the string array back to a single string.
$data = $string -split '\r?\n' -replace '\s*:\s*','=' | Out-String
or make sure you don't replace line break characters:
$data = $string -replace '[\t ]*:[\t ]*', '='
Edit:
Since your input data seems to consist of multiple records, not just one, you need to split the resulting string by record, so that you have individual strings per data set. Convert each data set to a hashtable with ConvertFrom-StringData, then convert those hashtables to custom objects.
$data = $string -split '(?<=\r?\n)\r?\n' | ForEach-Object {
$prop = $_.Trim() -split '\r?\n' -replace '\s*:\s*','=' |
Out-String |
ConvertFrom-StringData
New-Object -Type PSObject -Property $prop
}
In PowerShell v3 and newer you can use the [PSCustomObject] type accelerator instead of New-Object:
$data = $string -split '(?<=\r?\n)\r?\n' | ForEach-Object {
$prop = $_.Trim() -split '\r?\n' -replace '\s*:\s*','=' |
Out-String |
ConvertFrom-StringData
[PSCustomObject]$prop
}
The resulting list of objects can then be exported to a CSV.

Resources