Selecting XML node using powershell with Where clause - arrays

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<video publishState="Published" xmlns="urn:schemas-microsoft-com:msnvideo:catalog">
<uuid>ed255a56e807</uuid>
<title>ABCD</title>
<description>ABCD</description>
<startDate>2014-06-12T06:30:00Z</startDate>
<activeEndDate>2017-06-01</activeEndDate>
<searchableEndDate>2017-06-01</searchableEndDate>
<archiveEndDate>2019-05-22</archiveEndDate>
<videoFiles>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="400" width="320" height="180" fileSize="9654368" msnFileId="ED0ADD1E-7C36-472E-8186-5C43DDCCD58C" formatCode="101">
<uri>http://e2/ds/977d13e5-285f-4fe3-b49c-a6b604e89c22.mp4</uri>
</videoFile>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="600" width="640" height="360" fileSize="14911095" msnFileId="59AB7AE5-15F3-4EA8-ABD9-92E1A1CE0424" formatCode="102">
<uri>http://e2/ds/0aaad23d-bcb1-48e4-85c7-788b58ab8ae6/ds/3f4b77a3-3157-48a4-88b5-df854025cfe5.mp4</uri>
</videoFile>
<videoFile msnFileId="EDAA3798-B16B-435F-A9CA-6B1D07729D44" formatCode="1001" fileHash="ebc2f23aba9f0ff8d0146a052089a34f">
<uri>ftp://handler_20140611_highlight_b_222747.mp4</uri>
</videoFile>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="3000" width="1280" height="720" fileSize="70668740" msnFileId="9505AEF2-7742-4BB1-9427-F07185D6FC9B" formatCode="104">
<uri>http://e2/ds/a2e1563f-c9a3-41f7-97fe-e024742c6bbf.mp4</uri>
</videoFiles>
</video>
I am trying to get URI value where formatCode="1001". However since its an array, I am unable to do so. On getting to formatCode =1001 all values of array $uri are printed on console. I have tried piping Where clause e.g | Where {$xml.video.videoFiles.videoFile.formatCode -eq 1001 } but that too didn't help.
I am using the following code:
Get-ChildItem D:\video*.xml | ForEach-Object
{
$xml = [xml] (Get-Content $_.fullname)
# $xml.video.videoFiles.videoFile | Where-Object { $_.formatCode -eq 1001 } | Format-Table msnFileId, uri
$out = New-Object PSObject
$out | Add-Member NoteProperty UUID -Value $xml.video.uuid.innertext
$out | Add-Member NoteProperty EndDate -Value $xml.video.activeEndDate
Foreach ($uri in $xml.video.videoFiles.videoFile.uri)
{
if ($xml.video.videoFiles.videoFile.formatCode -eq 1001)
{
$temp1 = [string]$uri
}
}
$out | Add-Member NoteProperty URI -Value $temp1
Write-Output $out
}

XPath is a powerfull tool to select specific part of an XML. I know XPath but not powershell, I'm basing my answer from this post (your XML has similarity to the one I linked, both has default namespace), some modifications to the powershell script maybe required to make it fit your needs :
.....
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
$xml.SelectNodes("/ns:video/ns:videoFiles/ns:videoFile[#formatCode='1001']/ns:uri", $ns)
Or if you're sure formatCode is unique, you can use SelectSingleNode instead of SelectNodes.

Related

Adding array elements to a PSObject

Preface: I haven't any formal training with script writing. So I'm sure many of you will be banging you head against the table wondering what I'm doing.
I am working to gather information on our current catalog of Teams sites, and respective site owners. I have a PS script to gather the list of sites, then it loops through each site to get list of owners. After I gather that owner list (stored in a var) I loop through that var to get the user name and store each user name in an array as a new element. That element is then used later when adding a new member to a PSObject so I can export the results to a CSV. Here's a bit of the code;
#Var's
$Count = 1
$TSO = New-Object PSObject
$SiteOwner = #("SiteOwner0","SiteOwner1","SiteOwner2","SiteOwner3","SiteOwner4","SiteOwner5","SiteOwner6","SiteOwner7") #Array to create column headers in the CSV
#STEP 1: Get a list of the sites
$Sites = get-team | sort-object DisplayName
#STEP 2: Get a list of the owners of the different sites
FOREACH ($i in $Sites){
$h = Get-TeamUser -GroupID $i.GroupID | Where {$_.role -eq "Owner"}
$Count += 1
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Add the new columns to $TSO
$TSO | Add-Member -MemberType NoteProperty -Name "SiteName" -Value $i.DisplayName -Force
$TSO | Add-Member -MemberType NoteProperty -Name "GroupOwner" -Value $i.Description -Force
$TSO | Add-Member -MemberType NoteProperty -Name "Visibility" -Value $i.Visibility -Force
$TSO | Add-Member -MemberType NoteProperty -Name "Archived" -Value $i.Archived -Force
$TSO | Add-Member -MemberType NoteProperty -Name "SiteEmail" -Value $i.MailNickname -Force
#loop through each member to discover the assigned role
FOREACH ($o in $h){
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Build the dynamics of the owners before passing to $TSO
$OHolder += $o.user
} #END NESTED FOREACH
#Handle BLANK elements in the $OHolder array
#The number 8 is the number of array elements we need a value for
$Comp = 0
While($Comp -lt 8){
If($OHolder[$Comp] -ne $null){
$TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force
}
ELSEIF(-not ($OHolder[$Comp])){
$OHolder[$Comp] = "NA"
$TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force
}
#Increment the $Comp
$Comp += 1
}#END WHILE
#Resetting the $Comp outside the loop
$Comp = 0
#************* END STEP 2 *************************
#Squirt out the info to a CSV
$TSO | Export-CSV -Path $OPathOut -NoTypeInformation -Append -Force
#Reset the $OHOLDER Array
$OHolder = #()
} #END FOREACH
My problem is this; When adding the site owner to the PSObject for siteowner ($TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force) it's counting each character of the value as an element.
For example: $OHolder will have two elements, $OHolder[0] is supposed to equal "doe#domain.com" and $OHolder[1] is suppose to equal "john#domain.com". What actually happens is the length of the array becomes 29 (for each character) rather than 2.
How can I add the users to the PSObject as intended? Right now the output will have:
SiteOwner0 | SiteOwner1 | SiteOwner2
d | o | e
# |d |o
Do not use += to add items to an array. It is inefficient if the arrays become large. A new array is created each time after the current array contents are read into memory. You can simply output each item inside of the foreach and assign the array variable to the foreach output.
$OHolder = #(FOREACH ($o in $h){
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Build the dynamics of the owners before passing to $TSO
$o.user # Output
})
In the simplistic example above, a loop is likely not even necessary as $OHolder = ,$h.user should suffice in PowerShell v3+.
You are not defining $OHolder variable before using += operator.
By default powershell seems to define a string variable in this case.
$undefVar = $null
$undefVar += "test"
$undefVar += "some"
# content of $undefVar is now testsome
Try to move $OHolder = #() before the loop FOREACH ($o in $h){

Parsing a text file line by line and extracting string if it matches

We have a text log file similar to below with many users
<user>sandip</user>
something
<time>4:38 PM</time>
anything
<elapsed time> 60 mins </elapsed time>
We want to extract all users and we did the same simply by
Get-Content "C:\LOG\test.txt" | Select-String '(<user>.+</user>)' | ForEach-Object {
$_.Matches[0].Groups[1].Value
}
We want to parse test file line by line, check if it contains
user/time/elapsed time and [insert it in a dynamic variable if required] make a table of the same
Considering that your Log File follows the same format that i tested with:
(i.e something like this one:)
LogFile
This code should work just fine:
*
$Lines = get-content .\log.txt
$array = #()
foreach ($line in $lines)
{
if($line -like "<user>*")
{
$obj = New-Object psobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name "UserName" -value $line.Replace("<user>","").Replace("</user>","")
}
if($line -like "<time>*")
{
Add-Member -InputObject $obj -MemberType NoteProperty -Name "Time" -value $line.Replace("<time>","").Replace("</time>","")
}
if($line -like "<elapsed time>*")
{
Add-Member -InputObject $obj -MemberType NoteProperty -Name "ElapsedTime" -value $line.Replace("<elapsed time>","").Replace("</elapsed time>","")
$array += $obj
}
}
$array | Export-Csv .\test.csv
*
A quite compact solution using two regular expressions,
with a nonconsuming positive
lookahead to
split the source file into sections starting with <user>
inside an if to get -match the current line with an alternation and a backreference to insert the found key and value into the current Row of the new table. See this RegEx live on regex101.com
## Q:\Test\2018\06\22\SO_50988379.ps1
$Table = ForEach ($Section in ((Get-Content .\Test.log -raw) -split '(?=<user>)' -ne '')) {
$Row = New-Object psobject
ForEach ($Line in ($Section -split "`r?`n")) {
if($Line -match "<(user|time|elapsed time)>([^<]+)</\1>"){
Add-Member -InputObject $Row -MemberType NoteProperty `
-Name "$($Matches[1])" -value $Matches[2].Trim()
}
}
$Row
}
$Table #| Export-Csv .\test.csv
Sample output:
> .\SO_50988379.ps1
user time elapsed time
---- ---- ------------
sandip 4:38 PM 60 mins
Joshi 8:15 PM 60 mins

Powershell - foreach to array as job (local / multi-threaded)

I'm trying to parse a site to collect price and product details. The script works in a loop however it's very slow. So I'm trying to run a multi-threaded powershell script as a job.
I've tried a lot of suggestions but I'm struggling to get the results out even though I can see its working (the web-request screen flashing up)
I'm only selecting the last 10 but I'll put in a throttle later. Just can't get it to output. Essentially I'd like all results to flow back into $arr.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product,$arr)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
$arr += $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product,$arr
}
$data = Get-Job * | Receive-Job
#Show Array
$arr
So you would want to use runspaces for that. Runspaces is a pretty complicated thing, luckily we have Posh-RSJob which handles everything for you. https://github.com/proxb/PoshRSJob
You can pass in the script block, so you would need very little adjustments.
Probably something like this:
foreach ($product in $ImportedProducts){
Start-RSJob -ScriptBlock $ScriptBlock
}
Get-RSjob | Receive-RSJob
If you want to get the results into $arr, you can't do it from within the script block as you are attempting to do. Multiple script blocks running in parallel cannot be allowed to access a single copy of a variable without taking additional steps not worth getting into.
The answer to your problem is going to be to write the output of each script block as regular output. That output is buffered until you use Receive-Job to get the results out of the job at which time you capture it into the $arr variable in a single threaded manner. Below is cod which should get you most of the way there.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
Write-Output $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product
}
do {
$arr += Get-Job -State Completed | Receive-Job -AutoRemoveJob
} while (Get-Job -State Running)
#Show Array
$arr

Remove one or many members from Object in powershell

I have created a custom object called $info and moving it to an array $arr ,
How is it possible to remove one member along with its all properties ?
My script:
Get-Process | ForEach-Object{
$info = New-Object -TypeName PSObject
$info | Add-Member -Type NoteProperty -Name Process -Value $_.processname
$info | Add-Member -Type NoteProperty -Name ID -Value $_.id
$arr += $info
}
$arr | ft -AutoSize
The result looks like this :
Process ID
------- --
ApplicationFrameHost 38556
AppVShNotify 9792
armsvc 2336
atieclxx 6944
atiesrxx 1844
audiodg 59432
CcmExec 3988
chrome 46068
How can I remove one particular member for example "audiodg 59432" gets removed
audiodg 59432
Your terminology is a bit incorrect here. A member is on an individual object. When you use Add-Member above you're adding properties to each individual object, then you're returning an array of objects.
You're asking how to remove an individual object from the array.
In PowerShell you cannot remove an item from an array. You could instead filter the array based on some criteria and create a new one:
$newArr = $arr | Where-Object { $_.Name -ne 'audiodg' }
# or
$newArr = $arr | Where-Object { $_.ID -ne 59432 }

Create Powershell Object From Loop Containing Multiple Users

I need to create a Powershell object, array or hash table to store a list of users and assorted details, these are extracted from a CSV file and located using Get-ADUser. As below:
$userList = Import-CSV $CSVInputFile
$users = #{}
Foreach ($csvUser in $userList)
{
$userSearchString = $csvUser | Select -ExpandProperty SamAccountName
$currentUser = (Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"})
If ($currentUser.EmailAddress -ne $null)
{
$currentUserEmailString = $csvUser | Select -ExpandProperty EmailAddress
$currentUserEmailString = ($currentUserEmailString -as [string])
$currentUser.EmailAddress = $currentUserEmailString
}
$Users = New-Object PSObject -Property #{
DistinguishedName = $currentUser.DistinguishedName
EmailAddress = $currentUser.EmailAddress
Enabled = $currentUser.Enabled
GivenName = $currentUser.GivenName
Name = $currentUser.Name
PasswordExpired = $currentUser.PasswordExpired
PasswordLastSet = $currentUser.PasswordLastSet
SamAccountName = $currentUser.SamAccountName
Surname = $currentUser.Surname
}
$Users
}
How can I add the details of each user for each iteration of the loop to the object.
I want to end up with an object containing the details of a number of users, same as the output directly from Get-ADUser:
Name SamAccountName EmailAddress
---- -------------- ------------
User1 user1 user1#domain.com
User2 user2 user2#domain.com
User3 user3 user3#domain.com
Any help would be greatly appreciated!
Not sure if I'm missing the point on this but I see you are building a custom object right in your loop. The only issue I do see is you are not keeping the results after each loop. Rather you are destroying the objects history.
I would change the declaration of $users to an array $users = #() and instead of populating a user hashtable into users add the current object into the array. You will then have an array of hashtables:
$Users += New-Object PSObject -Property #{...
Then you could the $Users output line outside the loop and you will have the whole thing. Then you could just output to a Select to get the output you desire.
$Users | Select-Object name,SamAccountName,EmailAddress
There is a potential major drawback of this approach though. When using += on arrays a new array is created and resized for the new element and the old array is discarded. This has huge performance implications for larger arrays.
An even better way to approach this would be to leverage the pipeline. This would be a performance boost when you have larger user groups.
Import-CSV $CSVInputFile | ForEach-Object{
$userSearchString = $_.SamAccountName
$currentUser = Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"}
If ($currentUser.EmailAddress -ne $null){
$currentUser.EmailAddress = $_.EmailAddress
}
[pscustomobject][ordered]#{
DistinguishedName = $currentUser.DistinguishedName
# ..... truncated
Surname = $currentUser.Surname
}
}
Now you could send that to something like Export-CSV or just save it into a variable. Your options are open now. [pscustomobject][ordered] are type accelerators available in PowerShell v3.0+
Define an $users as Array
$users = #()
and append the New-Object into $Users.
$Users += New-Object
Can't believe both of you guys got in before me! Oh well.
Hope this helps anyway.
$userList = Import-CSV $CSVInputFile
$users = #()
Foreach ($csvUser in $userList)
{
$userSearchString = $csvUser | Select -ExpandProperty SamAccountName
$currentUser = (Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"})
If ($currentUser.EmailAddress -ne $null)
{
$currentUserEmailString = $csvUser | Select -ExpandProperty EmailAddress
$currentUserEmailString = ($currentUserEmailString -as [string])
$currentUser.EmailAddress = $currentUserEmailString
}
#clears the properties of the previous object and starts collecting properties
$UserObj = New-Object PSObject
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "DistinguishedName" -Value $($currentUser.DistinguishedName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "EmailAddress" -Value $($currentUser.EmailAddress)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "Enabled" -Value $($currentUser.Enabled)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "GivenName" -Value $($currentUser.GivenName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "UserName" -Value $($currentUser.Name)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "PasswordExpired" -Value $($currentUser.PasswordExpired)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "PasswordLastSet" -Value $($currentUser.PasswordLastSet)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "SamAccountName" -Value $($currentUser.SamAccountName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "Surname" -Value $($currentUser.Surname)
#saves the properties in an array that exists outside of the loop to preserve information beyond one interation
$users += $UserObj
}
$users | Format-Table -Property UserName,SamAccountName,EmailAddress

Resources