Trying to combine replace and new-item in powershell - powershell

I have a task to make changes to some Config files in a Directory, and the files that need changing are 7, all starting with "Monitoring_Tran_xx".
Within these files there are certain values (TransactionID="01" AgreedResponseTime="500" SearchProfileID="216") that need changing but not present across all 7, and will need to check if they are present before replace or creating them.
Also I am trying to insert a new parameter(using new-item) if a certain parameter is equal to a certain value e.g. if TemplateType = "4152" then create a new parameter next to it "DirectoryPoolID = '3' "
I will appreciate your help on this please.
Thanks
Example of Config file below:
<?xml *version="1.0"* ?>
<Monitor>
<Configuration OperatingMode="Transaction" LogFileName="C:\Program Files\*******s\MonitoringOMTR\MonitorLog_01.log" WriteFileInterval="120" ConnectionKey="**********" />
<TransactionMonitoringConfig TransactionID="01" AgreedResponseTime="500" SearchProfileID="216" />
<ShowMessages>
<Message Name="MOISearchStart" />
<Message Name="MOOSearchFound" />
<Message Name="MOOSearchEnd" />
<Message Name="MOOAlert" />
</ShowMessages>
<PerformanceCounters TransactionCount="191" TransactionBreaches="0" />
</Monitor>
Powershell script that i tried but it didn't quite result well:
(Get-Content -Path '***FS2072\UserData$\aroyet01\Desktop\HostPS.txt') |
if ([bool]((Get-Content -Path "***FS2072\UserData$\aroyet01\Desktop\HostPS.txt") -like '*DirectoryPool*', '*SearchProfile*')) {
write-host "Found it"
}
else {
write-host "Did not find it"
}
ForEach-Object {$_ -replace 'TransactionCount="191"', 'TransactionCount="196"'} |
Set-Content -Path '***FS2072\UserData$\aroyet01\Desktop\HostPS.txt'
Get-Content -Path '***FS2072\UserData$\aroyet01\Desktop\HostPS.txt'

I have a task to make changes to some Config files in a Directory, and the files that need changing are 7, all starting with "Monitoring_Tran_xx".
Stop treating your XML files as raw text - they can be parsed and treated with much better precision :)
First up, we need to parse the document(s) as XML, the easiest way probably being:
$xmlDocument = [xml](Get-Content C:\Path\To\)
Since you have multiple documents with a common naming pattern, we might use Get-ChildItem to discover the files on disk and loop over them to get the paths:
Get-ChildItem -Path C:\path\to\config\files\Monitoring_Tran_*.ps1 |ForEach-Object {
# read each document from disk with `Get-Content`, convert to [xml]
$xmlDocument = [xml]($_ |Get-Content)
}
Next, we need to be able to navigate our XML document. PowerShell has native syntax bindings for this:
$tmConfig = $xmlDocument.Monitor.TransactionMonitoringConfig
or, you can use XPath expressions to navigate the document as well:
$tmConfig = $xmlDocument.SelectSingleNode("/Monitor/TransactionMonitoringConfig")
Within these files there are certain values (TransactionID="01" AgreedResponseTime="500" SearchProfileID="216") that need changing but not present across all 7, and will need to check if they are present before replace or creating them
To check whether a named attribute exists on a node we can use the HasAttribute() method:
if($tmConfig.HasAttribute('TransactionID')){
# attribute exists, let's update it!
$tmConfig.SetAttribute('TransactionID', 123) # replace 123 with whatever ID you need
}
Also I am trying to insert a new parameter(using new-item) if a certain parameter is equal to a certain value e.g. if TemplateType = "4152" then create a new parameter next to it "DirectoryPoolID = '3' "
In the case where you want to add a new attribute to a node only if another attribute has a specific value, you can use GetAttribute() and SetAttribute():
if($xmlNode.GetAttribute('TemplateType') -eq "4152"){
$xmlNode.SetAttribute("DirectoryPoolID", 3)
}
Whenever you're done, save the document back to file:
Get-ChildItem -Path C:\path\to\config\files\Monitoring_Tran_*.ps1 |ForEach-Object {
# read each document from disk with `Get-Content`, convert to [xml]
$xmlDocument = [xml]($_ |Get-Content)
<# write all the code to inspect and update the attributes here #>
# write document back to disk
$xmlDocument.Save($_.FullName)
}

Looking at your code snippet i can only assist by giving you the advice to start with basic Powershell syntax documentation.
help about_If
help about_Pipelines
To give you an idea... Your if is placed in a pipeline. Your ForEach-Object is not placed in a pipeline. Both of these don't work the way you posted it. You can start from something like this:
$Content = '***FS2072\UserData$\aroyet01\Desktop\HostPS.txt'
if ($Content -condition 'Yourcondition') {
$Content | ForEach-Object {
$_ -replace 'Regex', 'Replacement'
}
} else {
'Your else here'
}
For editing the xml file you can take a look at this post as hinted by vonPryz in this question today.

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.

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.

Iterate through XML nodes / objects and pass to variables in Powershell

I'd be wanting to iterate through a set of XML and then pass those to variables which can be printed.
Here is an example of the data:
<applications>
<size>75</size>
<application>
<name>Applications 1</name>
<path>/Applications/Utilities/Application 1</path>
<version>10.14</version>
</application>
<application>
<name>Application 2</name>
<path>/Applications/Utilities/Application 2</path>
<version>6.3.9</version>
</application>
</applications
I've looked at using ForEach-Object when trying to output it but to no avail.
[string]$applicationProperties = $API.applications.application| ForEach-Object {
$_.name
$_.path
$_.version
}
This works but puts them all on one line, I'd like them so they print on individual lines but I couldn't prefix the $_ variable. I'm new to POSH as you can tell.
e.g. so I'd like to have name/path/version data saved to variables
[string]$applicationProperties = $API.applications.application | ForEach-Object {
[string]$name_var = $_.name
[string]$path_var = $_.path
[string]$version_var = $_.variable
}
This gives me one "application", but not all the possible objects. Also mentions that even when I'm putting down $name_var it's not accessing that variable? Do I need to do something to access that variable?
Any advice would be appreciated.
When you assign the output from ForEach-Object to [string]$applicationProperties, you're forcing PowerShell to convert all the strings into a single string because of the cast to [string].
What you'll want to do is create a new object for each application node that you're iterating over:
$appInformation = $API.applications.application | ForEach-Object {
# Create a new custom objects with the values from the XML nodes
[pscustomobject]#{
Name = $_.name
Path = $_.path
Version = $_.version
}
}
Now $appInformation will contain an array of objects each with a Name, Path and Version property. You can then further use and/or manipulate these objects in your scripts rather than just having a bunch of strings:
$appInformation |ForEach-Object {
Write-Host "Here is the version of application '$($_.Name)': $($_.Version)"
}
If you want to see them printed in the console with each property value on a separate line just pipe the array to Format-List:
$appInformation |Format-List

removing file versions from the file

I am trying to write down the powershell script which should remove the item version or language version "en" from all the files located in folder tour, if it finds "Title" section in the file.
My script is working only on top file name in the loop whose title is empty but not on other files in the loop whose title are empty as well. How can I change this script so that it can scroll through out the loop and remove the language version from each of these different files whose title is empty?
$SN1 = Get-ItemProperty -Path "master:/content/www/home/company/tour/*" -Name "Title"
$SN2 = ''
$SN3 =
foreach ($SItem in $SN1.Title) {
if ("$SItem" -eq $SN2)
{
Remove-ItemVersion -Path "master:/content/www/home/company/tour/*" -Language "en"
}
}
I'm not sure about the path you are selecting, Get-ItemProperty cannot use an * in its path to select every item in the path. You must get the items first and then pipe them into the Get-ItemProperty.
You need to create an array of every item and its properties and then loop through those.
$SN1 = Get-ChildItem -Path ""master:/content/www/home/company/tour" | Get-ItemProperty | select *
Then loop through each item:
$DebugPreference = 'Continue'
foreach ($SItem in $SN1)
{
Write-Debug -Message "Checking item $($SItem.Name) for a title."
if(!($SItem.Title))
{
Write-Debug -Message "A title does not exist for this item $($SItem.FullName), removing the language property of its ItemVersion."
Remove-ItemVersion -Path $SItem.FullName -Language "en"
}
}
As a note you'll want to use -Recurse on the Get-ChildItem if you have subdirectories you want to search through as well.
Looks like your basic syntax skills may need some additional study as for some reason you have quotes around the variable you are testing and are incorrectly using syntax for the title.
So first of all, change:
if ("$SItem" -eq $SN2)
To:
if (!($SItem))
Then, SN1 is ONLY the title as you have it written, so trying to do $SN1.title simply won't work. Also, what is the source you are querying?
EDIT: ANSWER BELOW:
Now that you provided the object type it is clear. Here is the code but you need to verify WHAT is getting deleted is the correct key/value:
$SN1 = Get-ItemProperty -Path "master:/content/www/home/company/tour/*" -Name "Title"
if ($SN1.Title -eq '')
{
Remove-ItemVersion -Path "master:/content/www/home/company/tour/*" -Language "en"
}
Unless I am missing something, based on the Get-Member $SN1 is a SINGLE item so there is no need to loop anything. If there are more than one somewhere, I have not seen indication in your code so you will need to provide more detail. This command reads the single Title, and if it is blank, go delete something else. For a COLLECTION of Titles, you need to provide where they live etc. for more help.

Search and print out information of .html datas in Powershell

I want to use Powershell to search in .html documents for specific strings and print them out.
Let me explain my first function which works:
I use this function to search for all .html documents in the path which contain the string "Tag". After that I search for the string "ID:", skip the tag "</TD><TD>" and use the following regular expression to print out the following 32 characters, which is the ID. Below you see a part of the html file and then my function.
<TR VALIGN=TOP><TD>Lokation:</TD><TD>\Test1\blabla\asdf\1234\WS Auswertungen</TD></TR>
<TR VALIGN=TOP><TD>Beschreibung:</TD><TD></TD></TR>
<TR VALIGN=TOP><TD>Eigentümer:</TD><TD><IMG ALIGN=MIDDLE SRC="file:///C:\Users\D0262290\AppData\Local\Temp\23\User.bmp"> Wilmes, Tanja</TD></TR>
<TR VALIGN=TOP><TD>ID:</TD><TD>55C7B7F411E2661E001000806C38EBA0</TD></TR>
</TABLE></TD><TD><IMG ALIGN=MIDDLE SRC="file:///C:\Users\D0262290\AppData\Local\Temp\23\User.bmp">
The function:
Function searchStringID {
Get-ChildItem -Path C:\Users\blub\lala\Dokus -Filter *.html |
Select-String -Pattern "Tag" |
select Path |
Get-ChildItem |
foreach {
if ((Get-Content -Raw -Path $_.FullName) -replace "<.*?>|\s" -match "(?s)ID:(?<Id>[a-z0-9]{32})" ) {
printToOutputLog
}
}
}
All this works fine.
Now I need to check for 2 more information and I can't figure out the regular expression I have to use because it has no fixed length of characters.
I always have to check for the string "Tag" in my problems below.
My first problem:
I have get the location of the file, so I gotta search for the string "Lokation:" (you can check it on the html I posted before).
So get the information I have have to skip the tags </TD><TD> again and use a regular expression to get the location. My problem here is that I have to idea how to manage the not-fixed length of characters. Is there a way to print out the characters between "Lokation:</TD><TD>" and "</TD></TR>" ?
The tags are all the same in the other html files, so I just need a solution which works for my example.
My second problem:
I have to read out the object's name. In the html document it's stored like this in a comment. The object's name begins after "[OBJECT:] and ends with "]". Here again, I can't figure out which expression I could use. The special characters in the example object's name below could be used.
<!-- ################################################################## -->
<!-- # [OBJECT: NAME BLA bla/ BLA_BLA 1 22:34] # -->
<!-- ################################################################## -->
I would be so thankful if anyone could help me. Every hint is useful to me because my brain is really stuck here.
Thanks and cheers
Ok, this one gets the contents of each file and runs each line through a Switch to match against three RegEx expressions. It worked for me against your sample data. It assigns each match to a variable for each of the three things you are looking for, and then outputs an object for each.
Function searchStringID {
Get-ChildItem -Path C:\Users\blub\lala\Dokus -Filter *.html |
Select-String -Pattern "Tag" |
select Path |
Get-ChildItem |
foreach {
Switch -Regex (Get-Content -Path $_.FullName){
"((?<=ID:.+?)[a-z0-9]{32})" {$ID = $Matches[1]}
"Lokation:.+?>(\\[^<]+)" {$Location = $Matches[1]}
"OBJECT: ?([^\]]+)" {$Object = $Matches[1]}
}
[PSCustomObject][Ordered]#{
'ID' = $ID
'Location' = $Location
'Name' = $Object
}
}
}
So then you could assign that to a variable and have an array of results to do with as you please (output to CSV? Sure! Display to the screen as a table? Can do! Email to the entire company? Um, yeah, but I wouldn't recommend that.)
Here's what it gave me when I ran it against your sample:
ID Location Name
-- -------- ----
55C7B7F411E2661E001000806C38EBA0 \Test1\blabla\asdf\1234\WS Auswertungen NAME BLA bla/ BLA_BLA 1 22:34