Using variables inside PowerShell replace - powershell

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)

Related

Powershell insert XML here-string into xml document

I'm trying to insert an XML here-string into a XMLdocument. However, the saved XMLdoc, shows: "System.Xml.XmlDocument", not the content. How can I fix this?
[xml] $Doc = New-Object System.Xml.XmlDocument
$updateNode = [xml] "<Update>
<Request>Test</Request>
</Update>"
#Create XML declaration
$declaration = $Doc.CreateXmlDeclaration("1.0","UTF-8",$null)
#Append XML declaration
$Doc.AppendChild($declaration)
#Create root element
$root = $Doc.CreateNode("element","BrowseDetailsRequest",$null)
#Create node based on Here-String
$node = $Doc.CreateElement("element",$updateNode,$null)
#Append node
$root.AppendChild($node)
#Append root element
$Doc.AppendChild($root)
Output at this moment:
<?xml version="1.0" encoding="UTF-8"?>
<BrowseDetailsRequest>
<System.Xml.XmlDocument />
</BrowseDetailsRequest>
You don't really manipulate the text in the xml. Use the objects to manipulate the xml. So you need to create an element for update and request and then assign the innertext value of request.
$Doc = New-Object System.Xml.XmlDocument
$declaration = $Doc.CreateXmlDeclaration("1.0","UTF-8",$null)
$Doc.AppendChild($declaration)
$root = $Doc.CreateNode("element","BrowseDetailsRequest",$null)
$elUpdate = $doc.CreateElement("element","Update",$null)
$elRequest = $doc.CreateElement("element","Request",$null)
$elRequest.InnerText = "Test"
$elUpdate.AppendChild($elRequest)
$root.AppendChild($elUpdate)
$doc.AppendChild($root)

Get-GPOReport and Search For Matched Name Value

I'm trying to use the PowerShell command 'Get-GPOReport' to get GPO information in XML string format so I can search it for sub-Element values with unknown and different Element tag names (I don't think XML Object format will work for me, so I didn't perform a cast with "[xml]"), but I haven't been able to parse the XML output so that I can grab the line or two after a desired "Name" Element line that matches the text I'm searching for.
After, I have been trying to use 'Select-String' or 'Select-XML' with XPath (formatting is unclear and I don't know if I can use a format for various policy record locations) to match text and grab a value, but I haven't had any luck.
Also, if anyone know how to search for GPMC GUI names (i.e. "Enforce password history") instead of needing to first locate back-end equivalent names to search for (i.e. "PasswordHistorySize"), that would also be more helpful.
The following initial code is the part that works:
$String = "PasswordHistorySize" # This is an example string, as I will search for various strings eventually from a file, but I'm not sure if I could search for equivalent Group Policy GUI text "Enforce password history", if anyone knows how to do that.
$CurrentGPOReport = Get-GPOReport -Guid $GPO.Id -ReportType Xml -Domain $Domain -Server $NearestDC
If ($CurrentGPOReport -match $String)
{
Write-Host "Policy Found: ""$($String)""" -Foregroundcolor Green
#
#
# The following code is what I've tried to use to get value data, without any luck:
#
$ValueLine1 = $($CurrentGPOReport | Select-String -Pattern $String -Context 0,2)
$Value = $($Pattern = ">(.*?)</" ; [regex]::match($ValueLine1, $Pattern).Groups[1].Value)
}
I've been looking at this since yesterday and didn't understand why Select-String wasn't working, and I figured it out today... The report is stored as a multi-line string, rather than an array of strings. You could do a -match against it for the value, but Select-String doesn't like the multi-line formatting it seems. If you -split '[\r\n]+' on it you can get Select-String to find your string.
If you want to use RegEx to just snipe the setting value you can do it with a multi-line regex search like this:
$String = "PasswordHistorySize" # This is an example string, as I will search for various strings eventually from a file, but I'm not sure if I could search for equivalent Group Policy GUI text "Enforce password history", if anyone knows how to do that.
$CurrentGPOReport = Get-GPOReport -Guid $GPO.Id -ReportType Xml -Domain $Domain -Server $NearestDC
$RegEx = '(?s)' + [RegEx]::Escape($String) + '.+?Setting.*?>(.*?)<'
If($CurrentGPOReport -match $RegEx)
{
Write-Host "Policy Found: ""$String""" -Foregroundcolor Green
$Value = $Matches[1]
}
I'm not sure how to match the GPMC name, sorry about that, but this should get you closer to your goals.
Edit: To try and get every setting separated out into it's own chunk of text and not just work on that one policy I had to alter my RegEx a bit. This one's a little more messy with the output, but can be cleaned up simply enough I think. This will split a GPO into individual settings:
$Policies = $CurrentGPOReport -split '(\<(q\d+:.+?>).+?\<(?:\/\2))' | Where { $_ -match ':Name' }
That will get you a collection of things that look like this:
<q1:Account>
<q1:Name>PasswordHistorySize</q1:Name>
<q1:SettingNumber>21</q1:SettingNumber>
<q1:Type>Password</q1:Type>
</q1:Account>
From there you just have to filter for whatever setting you're looking for.
I have tried this with XPath, as you'll have more control navigating in the XML nodes:
[string]$SearchQuery = "user"
[xml]$Xml = Get-GPOReport -Name "Default Domain Policy" -ReportType xml
[array]$Nodes = Select-Xml -Xml $Xml -Namespace #{gpo="http://www.microsoft.com/GroupPolicy/Settings"} -XPath "//*"
$Nodes | Where-Object -FilterScript {$_.Node.'#text' -match $SearchQuery} | ForEach-Object -Process {
$_.Name #Name of the found node
$_.Node.'#text' #text in between the tags
$_.Node.ParentNode.ChildNodes.LocalName #other nodes on the same level
}
After testing we found that in the XML output of the Get-GPOReport cmdlet, the setting names does not always match that of the HTML output. For example: "Log on as a service" is found as "SeServiceLogonRight" in the XML output.

Fill a variable in a condition from xml

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.

cutting a portion of url in powershell

I have a few URLs which would need to cut and separate the first part of the each URL, i.e example1.com, example2.com, example3.com from each line and store in a variable
Contents in url.csv
https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4
https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8
https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6
Script:
$oldurl = Import-CSV "url.csv"
$newurl = $oldurl.list -replace "https://"
This would replace https://, however the rest of each cannot be hard coded as those values can change.
What could be change code change required to cut anything from and after /v1/ along with https://?
$list = #(
"https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4",
"https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8",
"https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6"
)
$result = $list | %{
$uri = [System.Uri] $_
$uri.Authority
}
$result
See System.Uri properties to potentially assemble the information you need in your result list.
This will cut off anything after "/v1/" and it self. Is that what you want?
$string = "https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4"
$string = $string -replace "https://"
$pos = $string.IndexOf("/v1/")
$result = $string.Substring(0, $pos)
$result
Output: example1.com

Modify xml while preserving whitespace

I'm running into several problems trying to replace an attribute in an XML file while preserving whitespace.
Attempt 1
$xml = [xml](get-content data.xml)
$xml.Path.To.Attribute = $value
set-content data.xml [String]$value
Result: Insigificant whitespace (namely newlines) are removed
Attempt 2
$xml = new-object xml
$xml.PreserveWhitespace = true
$xml.PreserveWhitespace
Result: PreserveWhitespace remains false
Attempt 3
$xml = get-content data.xml
$xml = [regex]::replace($xml, "pattern", "replacement")
set-content data.xml $xml
Result: [regex]::replace messes up the line endings
Am I taking crazy pills here?
The problems were all related: Get-Content returns lines of the text file, not the text itself. When cast back to a string, the lines are combined outright.
The best solution was to use:
$xml = [xml]([System.IO.File]::ReadAllText("data.xml"))
This isn't working because PreserveWhiteSpace is a boolean:
$xml = new-object xml
$xml.PreserveWhitespace = true
$xml.PreserveWhitespace
Use:
xml.PreserveWhitespace = $true
By default empty lines are ignored, in order to preserve them you can change PreserveWhitespace property before reading the file:
Create XmlDocument object and configure PreserveWhitespace:
$xmlDoc = [xml]::new()
$xmlDoc.PreserveWhitespace = $true
Load the document:
$xmlDoc.Load($myFilePath)
or
$xmlDoc.LoadXml($(Get-Content $myFilePath -Raw))