Fill a variable in a condition from xml - powershell

what I have is a variable called $usercity.city obtained with a Msoluser query to get a user city.
What I need to do is to populate a variable called $icthead depending from that value. I mean I have like 30 cities with differents icthead, is there a way different from a simple switch to do that?
Like loading from an external XML or stuff like that... thanks! If you think a simple switch won't slow down my script I'm gonna go that way.
A pratic example of what I want to do is:
$usercity=Milan then $icthead=email1#company.it
$usercity=London then $icthead=email2#company.it
$usercity=chicago then $icthead=email3#company.it
but for 20 or more cities. A solution would be loading every email on an array[$usercity] but I can't do it by myself cause I'm really bad. Thanks in advance.

I'm not sure if something like this is what you are looking for, but if you have the data in an excel sheet with the e-mail for each city in a row of data, then you can grab the e-mail of that city this way:
foreach($city in $usercity.city)
{
icthead
function icthead {
# This part is to open up the designated xlsx file and make powershell "look" in it.
$Excel = New-Object -ComObject excel.application
$DestinationPath = "Your xlsx file location"
$workbook = $Excel.workbooks.open($DestinationPath)
$Sheet = $workbook.worksheets.item("Name of the excel tab your information is in")
$Sheet.activate()
# This will find the horizontal row that the city is in.
$GetCity = $Sheet.Range("Vertical range .ex B2:B9999").find("$City")
$Row = $GetCity.Row
# This will require you to download the ImportExcel module - https://www.powershellgallery.com/packages/ImportExcel/3.0.0 - All credit to Douglas Finke
$XL = Import-Excel "Your xlsx file location"
# .Mail is an example. Should be your column title. It will grab the value that is in that row under that column and save it in the variable. Remember that xlsx is 0 indexed.
$CityMail = $XL[$row].Mail
}
$IctHead = $CityMail
Write-host $IctHead
}

I think you are looking for an external reference. It can be done by many ways. Here is some of those:
Hashtable/Dictionary variable:
#declare hashtable type variable.
$ht = #{}
#populate it with key/value items.
$ht.Add('Milan','email1#company.it')
$ht.Add('London','email2#company.it')
$ht.Add('Chicago','email3#company.it')
#input city name Milan
$city = 'Milan'
#now call hashtable variable by its key Milan
$ht[$city]
#output
email1#company.it
#usage example
Set-UserProperty -City $city -Email $ht[$city]
XML string:
[xml]$xml = #"
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<City name="Milan">
<Email>email1#company.it</Email>
</City>
<City name="London">
<Email>email2#company.it</Email>
</City>
<City name="Chicago">
<Email>email3#company.it</Email>
</City>
</Dictionary>
"#
$xml.Dictionary.City | Where-Object {$_.name -eq $city} | Select -ExpandProperty Email
CSV string:
$city = 'Milan'
$CSV = #"
"City","Email"
"Milan","email1#company.it"
"London","email2#company.it"
"Chicago","email3#company.it"
"# | ConvertFrom-Csv
($CSV | Where-Object {$_.City -eq $city}).Email
#output
email1#company.it
XML/CSV files:
Create either a XML or a CSV file with the same content as those of the respective here string (#" ... "#). E.g:
myfile.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<City name="Milan">
<Email>email1#company.it</Email>
</City>
<City name="London">
<Email>email2#company.it</Email>
</City>
<City name="Chicago">
<Email>email3#company.it</Email>
</City>
</Dictionary>
myfile.csv:
"City","Email"
"Milan","email1#company.it"
"London","email2#company.it"
"Chicago","email3#company.it"
Replace the here string (#" ... "#) in the code above with Get-Content -Raw -Path 'myfile.xml' or Get-Content -Raw -Path 'myfile.csv', respectively. E.g:
[xml]$xml = Get-Content -Raw -Path 'myfile.xml'
or
$CSV = Get-Content -Raw -Path 'myfile.csv'
The rest of the code stays the same.

Related

Powershell - randomize same string in huge file using all random strings from array

I am looking for a way to randomize a specific string in a huge file by using predefined strings from array, without having to write temporary file on disk.
There is a file which contains the same string, e.g. "ABC123456789" at many places:
<Id>ABC123456789</Id><tag1>some data</tag1><Id>ABC123456789</Id><Id>ABC123456789</Id><tag2>some data</tag2><Id>ABC123456789</Id><tag1>some data</tag1><tag3>some data</tag3><Id>ABC123456789</Id><Id>ABC123456789</Id>
I am trying to randomize that "ABC123456789" string using array, or list of defined strings, e.g. "#('foo','bar','baz','foo-1','bar-1')". Each ABC123456789 should be replaced by randomly picked string from the array/list.
I have ended up with following solution, which is working "fine". But it definitely is not the right approach, as it do many savings on disk - one for each replaced string and therefore is very slow:
$inputFile = Get-Content 'c:\temp\randomize.xml' -raw
$checkString = Get-Content -Path 'c:\temp\randomize.xml' -Raw | Select-String -Pattern '<Id>ABC123456789'
[regex]$pattern = "<Id>ABC123456789"
while($checkString -ne $null) {
$pattern.replace($inputFile, "<Id>$(Get-Random -InputObject #('foo','bar','baz','foo-1','bar-1'))", 1) | Set-Content 'c:\temp\randomize.xml' -NoNewline
$inputFile = Get-Content 'c:\temp\randomize.xml' -raw
$checkString = Get-Content -Path 'c:\temp\randomize.xml' -Raw | Select-String -Pattern '<Id>ABC123456789'
}
Write-Host All finished
The output is randomized, e.g.:
<Id>foo
<Id>bar
<Id>foo
<Id>foo-1
However, I would like to achieve this kind of output without having to write file to disk in each step. For thousands of the string occurrences it takes a lot of time. Any idea how to do it?
=========================
Edit 2023-02-16
I tried the solution from zett42 and it works fine with simple XML structure. In my case there is some complication which was not important in my text processing approach.
Root and some other elements names in the structure of processed XML file contain colon and there must be some special setting for "-XPath" for this situation. Or, maybe the solution is outside of Powershell scope.
<?xml version='1.0' encoding='UTF-8'?>
<C23A:SC777a xmlns="urn:C23A:xsd:$SC777a" xmlns:C23A="urn:C23A:xsd:$SC777a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:C23A:xsd:$SC777a SC777a.xsd">
<C23A:FIToDDD xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02">
<CxAAA>
<DxBBB>
<ABC>
<Id>ZZZZZZ999999</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</CxAAA>
<CxAAA>
<DxBBB>
<ABC>
<Id>ZZZZZZ999999</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</CxAAA>
</C23A:FIToDDD>
<C23A:PmtRtr xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.004.001.02">
<GrpHdr>
<TtREEE Abc="XV">123.45</TtREEE>
<SttlmInf>
<STTm>ABCA</STTm>
<CLss>
<PRta>SIII</PRta>
</CLss>
</SttlmInf>
</GrpHdr>
<TxInf>
<OrgnlTxRef>
<DxBBB>
<ABC>
<Id>YYYYYY888888</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</OrgnlTxRef>
</TxInf>
</C23A:PmtRtr>
</C23A:SC777a>
As commented, it is not recommended to process XML like a text file. This is a brittle approach that depends too much on the formatting of the XML. Instead, use a proper XML parser to load the XML and then process its elements in an object-oriented way.
# Use XmlDocument (alias [xml]) to load the XML
$xml = [xml]::new(); $xml.Load(( Convert-Path -LiteralPath input.xml ))
# Define the ID replacements
$searchString = 'ABC123456789'
$replacements = 'foo','bar','baz','foo-1','bar-1'
# Process the text of all ID elements that match the search string, regardless how deeply nested they are.
$xml | Select-Xml -XPath '//Id/text()' | ForEach-Object Node |
Where-Object Value -eq $searchString | ForEach-Object {
# Replace the text of the current element by a randomly choosen string
$_.Value = Get-Random $replacements
}
# Save the modified document to a file
$xml.Save( (New-Item output.xml -Force).Fullname )
$xml | Select-Xml -XPath '//Id/text()' selects the text nodes of all Id elements, regardless how deeply nested they are in the XML DOM, using the versatile Select-Xml command. The XML nodes are selected by specifying an XPath expression.
Regarding your edit, when you have to deal with XML namespaces, use the parameter -Namespace to specify a namespace prefix to use in the XPath expression for the given namespace URI. In this example I've simply choosen a as the namespace prefix:
$xml | Select-Xml -XPath '//a:Id/text()' -Namespace #{a = 'urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02'}
ForEach-Object Node selects the Node property from each result of Select-Xml. This simplifies the following code.
Where-Object Value -eq $searchString selects the text nodes that match the search string.
Within ForEach-Object, the variable $_ stands for the current text node. Assign to its Value property to change the text.
The Convert-Path and New-Item calls make it possible to use a relative PowerShell path (PSPath) with the .NET XmlDocument class. In general .NET APIs don't know anything about the current directory of PowerShell, so we have to convert the paths before passing to .NET API.

Powershell CSV Variable Issue

I am having a problem with passing a variable into a CSV. I need to pass an email for a spreadsheet showing all skills. It's the same email for each skill. I just want the $email to populate my csv. It does not pass and only shows the $email instead of the test#test.com in the column.
I am new at powershell so any guidance is greatly appreciated. Thank you.
------------here is my script-------------------------
Add-Content -Path C:\temp\test.csv -Value '"User Name","Skill Name","Level"'
$Email = "test#test.com"
$agent = #(
}
'"$Email","T1","4"'
'"$Email","T2","6"'
'"$Email","T3","7"'
'"$Email","Training","1"'
'"$Email","Supervisor","8"'
)
$agent | foreach { Add-Content -Path C:\temp\temp.csv -Value $_ }
Because you are new to Powershell, I'm showing you two alternative ways of framing the problem. These might help you get used to some of the features of powershell.
$Email = 'test#test.com'
$mytext = #"
"User Name","Skill Name","Level"
"$Email","T1","4"
"$Email","T2","6"
"$Email","T3","7"
"$Email","Training","1"
"$Email","Supervisor","8"
"#
$mytext | Out-file Mycsv.csv
Here, I just set up the Email variable, then create one big here string with the header and the five data records in it. Because I used double quotes on the here string, the variable $Email will be detected inside of it. A here string with single quotes would not have behaved correctly.
Then, I pass $mytext through a pipeline one line at a time, and Out-file collects all this into a file.
Here's the second approach:
$Email = 'test#test.com'
$myarray = #(
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T1"; "Level" = 4}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T2"; "Level" = 6}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "T3"; "Level" = 7}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "Training"; "Level" = 1}
[PsCustomobject]#{"User Name" = $Email; "Skill Name" = "Supervisor"; "Level" = 8}
)
$myarray | Export-Csv myothercsv.csv
Here, I set up the variable Email, then create an array of custom objects, each with the same named properties.
Then I pass the array through a pipeline to Export-Csv which converts everything to Csv format. It's worth noting that Export-Csv V5 throws in a line that says #TYPE in it. This is not hard to eliminate, using the notype parameter if desired. It's also worth noting that the double quotes in the output file were all added in by Export-csv, and weren't copies of the double quotes in the script.
Edit. Pipelines are a surprisingly easy and flexible way of getting things done in powershell. For this reason, cmdlets like Out-File and Export-Csv are built to work well with pipelines supplying a stream of input. A lot of loop control, initialization, and finalization busy work is being handled behind the scenes by PS.

ConvertFrom-StringData clear hash-table

I am attempting to parse multiple mails in a specific mail folder, to eventually create a csv file with the information.
If I run the script on one user, the output I get is perfect;
Name Value
---- -----
Kontor. HR
Prøvetid Ja
Blomster på dagen Nej
Telefonnr. 85789845
Adresse Hovedgade 13
Ansættes i stedet for Ninette Kock
Navn Marianne Pedersen
Etage 1. sal
Fri telefon Ja
Benyttet navn Marianne Pedersen
Medarbejdertype Fastansat
Overtagelse af mobilnr. Ja
Placering Sydøst
Ansættelsesdato 01-01-2020
Stilling Fuldmægtig
Lokalnr. +45 3370 0000
Besked til IT-centret
Antal timer 37
Journalistpraktikant Nej
Tidl. ansat Nej
Initialer MAPE
Blomster anden dag 02-01-2020
Postnr/By 2630 Taastrup
Cpr. nr. XXXX
The problem is when I try to parse a second mail, the first mail will still get parsed correctly, but the second one will throw an error;
ConvertFrom-StringData : Data item 'Medarbejdertype' in line 'Medarbejdertype=Fastansat' is
already defined.
If I try to parse the second mail as the first mail, it will parse perfectly.
Problem happens when I try to parse more then one.
I have tried to clear the hash-table like so;
$data.clear()
clear-variable -name data
$data = #{}
But it somehow seems that the hash-table is not emptied?
The code I am using:
Clear-Host
$navne = "MAJE", "ASHO"
# create an instance of Outlook
Add-Type -AssemblyName "Microsoft.Office.Interop.Outlook"
$outlook = New-Object -ComObject Outlook.Application
$nameSpace = $outlook.GetNameSpace("MAPI")
$html = New-Object -ComObject "HTMlFile"
# Opens and save all mails in Nye Til Oprettelse in $mails.
$mails = $nameSpace.Folders.Item('hotline').Folders.Item('Indbakke').Folders.Item('#HOVSA''er').Folders.Item('#01Nye til oprettelse').Items | Select-Object -Property subject, htmlbody
# Loop over $mails, check if $navn = subject
foreach ($navn in $navne) {
foreach ($mail in $mails) {
if($mail | Where-Object {$_.subject -match "$Navn"}) {
#$mail.HTMLBody
# write data to $html, then find all <table> tags and parse that text.
$html.IHTMLDOCUMENT2_write($mail.HTMLBody)
$data = $html.all.tags("table") | % innertext
$data = $data -split '\r?\n' -match '^[^:]+:[^:]+$' -replace ':\s*', '=' -join "`n" | ConvertFrom-StringData
$navn
$data
$data = #[]
}
}
}
You're appending each email to the $html document using $html.IHTMLDOCUMENT2_write($mail.HTMLBody), but you only initialise it once at the top of your script, so each time your foreach ($mail in $mails) loops you add another email to the document.
That means the second time the loop iterates there are two emails in the $html document and you're getting an error about duplicate data items as a result.
Try moving $html = New-Object -ComObject "HTMlFile" inside your foreach loop so it reads:
foreach ($mail in $mails) {
...
$html = New-Object -ComObject "HTMlFile"
$html.IHTMLDOCUMENT2_write($mail.HTMLBody)
...
}
P.S.: You might also want to look at using a non-COM library like the HTML Agility Pack to do your html processing - it'll potentially be a bit faster and more reliable (and portable) in an automation script than COM is.

Using variables inside PowerShell replace

I'm trying to add some new settings to a tomcat server.xml file datasource. I can match the last setting in the datasource, which has a password that I need to capture, but when I try to replace it, I'm not see any changes.
$serverXml = "C:\server.xml"
$xml = Get-Content $serverXml
$password = (($xml -match " password=""(.*)""").Replace(' password="', "").Replace('" />', ''))[0]
$oldString = #"
username="cf.user"
password="$password" />
"#
$newString = #"
username="cf.user"
password="$password"
testWhileIdle="true"
testOnBorrow="true"
testOnReturn="false"
validationQuery="select 1"
validationInterval="30000"
minEvictableIdleTimeMillis="30000" />
"#
$xml = $xml.replace($oldString, $newString)
Set-Content -Path $serverXml -Value $xml
I'm able to match the $password fine, but when I'm using it as a variable to pass into $oldString and $newString in the replace, its not matching anymore. Even $xml -match $oldString doesn't return anything, but totally should as far as I can tell.
Do not edit XML via string replacements. Use the gratuitous XML parser PowerShell provides you with.
Load the config file like this:
[xml]$xml = Get-Content $serverXml
or like this:
$xml = New-Object Xml.XmlDocument
$xml.Load($serverXml)
The latter is a bit more failsafe, because it will (for instance) check that the encoding of the file actually matches the encoding specified in the preamble.
Select nodes via XPath expressions:
$node = $xml.SelectSingleNode('/Path/To/Node')
Change existing attributes like this:
$node.Attributes['password'] = 'newpassword'
Add new attributes like this:
$attr = $xml.CreateAttribute('testWhileIdle')
$attr.Value = 'true'
[void]$node.Attributes.Append($attr)
Then save the modified XML back to the file:
$xml.Save($serverXml)

String matching in PowerShell

I am new to scripting, and I would like to ask you help in the following:
This script should be scheduled task, which is working with Veritas NetBackup, and it creates a backup register in CSV format.
I am generating two source files (.csv comma delimited):
One file contains: JobID, FinishDate, Policy, etc...
The second file contains: JobID, TapeID
It is possible that in the second file there are multiple same JobIDs with different TapeID-s.
I would like to reach that, the script for each line in source file 1 should check all of the source file 2 and if there is a JobID match, if yes, it should have the following output:
JobID,FinishDate,Policy,etc...,TapeID,TapeID....
I have tried it with the following logic, but sometimes I have no TapeID, or I have two same TapeID-s:
Contents of sourcefile 1 is in $BackupStatus
Contents of sourcefile 2 is in $TapesUsed
$FinalReport =
foreach ($FinalPart1 in $BackupStatus) {
write-output $FinalPart1
$MediaID =
foreach ($line in $TapesUsed){
write-output $line.split(",")[1] | where-object{$line.split(",")[0] -like $FinalPart1.split(",")[0]}
}
write-output $MediaID
}
If the CSV files are not huge, it is easier to use Import-Csv instead of splitting the files by hand:
$BackupStatus = Import-Csv "Sourcefile1.csv"
$TapesUsed = Import-Csv "Sourcefile2.csv"
This will generate a list of objects for each file. You can then compare these lists quite easily:
Foreach ($Entry in $BackupStatus) {
$Match = $TapesUsed | Where {$_.JobID -eq $Entry.JobID}
if ($Match) {
$Output = New-Object -TypeName PSCustomObject -Property #{"JobID" = $Entry.JobID ; [...] ; "TapeID" = $Match.TapeID # replace [...] with the properties you want to use
Export-Csv -InputObject $Output -Path <OUTPUTFILE.CSV> -Append -NoTypeInformation }
}
This is a relatively verbose variant, but I prefer it like this.
I am checking for each entry in the first file whether there is a matching entry in the second. If there is one I combine the required fields from the entry of the first list with the ones from the entry in the second list into one object that I can then export very comfortably using Export-Csv.