Convert a SharePoint Online list into JSON using arrays - arrays

I'm trying to convert a set of SharePoint list items (and associated data) into a JSON object. To do this I'm trying to create a multi-dimensional array and then iterate over my SharePoint objects to populate it.
This is the relevant code so far:
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = #()
$listArray["DisplayTitle"] = #()
$listArray["Description"] = #()
$listArray["Setting"] = #()
$listArray["HealthAreas"] = #()
$listArray["ResourceType"] = #()
$listArray["ExternalURL"] = #()
$listArray["Active"] = #()
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray["DisplayTitle"].Add($item["Title"])
$listArray["Description"].Add($item["File Description"])
$listArray["Setting"].Add($item["Setting"])
$listArray["HealthAreas"].Add($item["Health_x0020_Area"])
$listArray["ResourceType"].Add($item["Resource_x0020_Type"])
$listArray["ExternalURL"].Add($item["External_x0020_file_x0020_path"])
$listArray["Active"].Add($item["Currently_x0020_active_x003f_"])
}
Write-Host "############################"
Write-Host $listArray | ConvertTo-Json
I know there's a gap in my thinking here (maybe I need a hashtable) but just can't see it. The error I'm receiving is:
You cannot call a method on a null-valued expression.
However I can't see where my null variable may be originating from as I've confirmed each item in the loop does contain data (by writing to console).

The error that you receive is not related to SharePoint but to PowerShell. You created the PowerShell array and tried to access its elements like it was associative array/hashtable.
Please try this code (I've tested it with my own list with different column names and it works fine):
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = New-Object System.Collections.Generic.List[System.Object]
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray.Add([hashtable]#{
DisplayTitle=$item["Title"];
Description= $item["File Description"];
Setting= $item["Setting"];
HealthAreas= $item["Health_x0020_Area"];
ResourceType= $item["Resource_x0020_Type"];
ExternalURL= $item["External_x0020_file_x0020_path"];
Active= $item["Currently_x0020_active_x003f_"];
}
)
}
Write-Host "############################"
$json = $listArray | ConvertTo-Json
Write-Host $json

Related

values from a foreach loop in a function into an array

I have a function that replaces PackageID in a SCCM task sequence, I would like to capture all those package IDs into a variable, so I would be able to create a report based on that.
The problem is that I already have a foreach loop doing the work, and I can't figure out how to not overwrite the values.
$Driver.PackageID comes from a foreach loop based on $Drivers, which contains
If I run the code I get this as I have Write-Output defined:
Updated code:
function Set-Drivers{
foreach ($Driver in $Drivers) {
Write-Output "Driver Name: $($Driver.Name)"
Write-Output "DriverPackageID: $($Driver.PackageID)"
}
}
$array = #()
$array = Set-Drivers
$hash = [ordered]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
$array += New-Object -Typename PSObject -Property $hash
Can someone explain, why I only get the first result in my $array? I can see the values are being overwritten if I run it in debug mode.
Your code is not iterating over the results, but instead only using one of them. This what you intended.
$array = $drivers | foreach {
[ordered]#{
DriverName = $_.Name
DriverID = $_.PackageID
}
}
Your function doesn't return anything. It only writes lines to the console. Then after the function is finished, you create a single object and add that to your array.
Try something like
function Set-Drivers{
$result = foreach ($Driver in $Drivers) {
[PsCustomObject]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
}
# output the result
# the comma wraps the result in a single element array, even if it has only one element.
# PowerShell 'flattens' that upon return from the function, leaving the actual resulting array.
,$result
}
$array = Set-Drivers
# show what you've got
$array

Add a String to a value in an Array Powershell

I have got Strings in an array which are the names of groups. Now I would like to modify those values and connect an other String to the beginning of this String
$Groups = Get-ADPrincipalGroupMembership $User $GroupArray = #()
foreach ($Group in $Groups)
{
$GroupArray += ($Group | select name)
}
echo $("Domain\" + $GroupArray[0])
This prints something like:
Domain\#{name=Domain Users}
However I would like to get something like:
Domain\Domain Users
Change it to arraylist if you wish to add all the values:
$Groups = Get-ADPrincipalGroupMembership $User
$arraylist = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$arraylist.Add($Group.Name) | Out-Null
}
$arraylist
PS: You can display the result collating with the Domain however you want; I have not touched that section. Hope it helps.

Using Invoke-WebRequest on an array in PowerShell

I'm trying write a script that will grab the fortune 100 URLs from here, put those into an array, and then write a runspace that uses Invoke-WebRequest to get the content of those URLs and writes that content to a file. This is the code that I have so far:
#Importing Modules
Import-Module PoshRSJob
#variable declaration
$page = Invoke-WebRequest https://www.zyxware.com/articles/4344/list-of-fortune-500-companies-and-their-websites
$links = $page.Links
$tables = #($page.ParsedHtml.GetElementsByTagName("TABLE"))
$tableRows = $tables[0].Rows
#loops through the table to get only the top 100 urls.
$urlArray = #()
foreach ($tablerow in $tablerows) {
$urlArray += New-Object PSObject -Property #{'URLName' = $tablerow.InnerHTML.Split('"')[1]}
#Write-Host ($tablerow.innerHTML).Split('"')[1]
$i++
if ($i -eq 101) {break}
}
#Number of Runspaces to use
#$RunspaceThreads = 1
#Declaring Variables
$ParamList = #($urlArray)
$webRequest = #()
$urlArray | start-rsjob -ScriptBlock {
#$webRequest = (Invoke-WebRequest $using:ParamList)
#Invoke-WebRequest $urlArray
#Invoke-WebRequest {$urlArray}
#Get-Content $urlArray
}
The problem that I'm running into right now is that I can't get Invoke-WebRequest or Get-Content to give me the contents of the URLs that are actually contained in the array. You can see that in the scriptblock, I commented out some lines that didn't work.
My question is: using a runspace, what do I need to do to pull the data from all the URLs in the array using Get-Content, and then write that to a file?
You can adjust your current query to get the first 100 company names. This skips the empty company at the front. Consider using [PSCustomObject] #{ URLName = $url } which replaces the legacy New-Object PSObject.
$urlArray = #()
$i = 0
foreach ($tablerow in $tablerows) {
$url = $tablerow.InnerHTML.Split('"')[1]
if ($url) {
# Only add an object when the url exists
$urlArray += [PSCustomObject] #{ URLName = $url }
$i++
if ($i -eq 100) {break}
}
}
To run the requests in parallel use Start-RSJob with a script block. Invoke-Webrequest is then run in parallel. Note that in this example $_ refers to the current array element that is piped which consists of an object with a URLName property, but you need to be a little careful what variables you use inside the scriptblock because they might not be resovled they way you expect them to be.
# Run the webrequests in parallel
# $_ refers to a PSCustomObject with the #{ URLName = $url } property
$requests = ($urlArray | start-rsjob -ScriptBlock { Invoke-WebRequest -Uri $_.URLName })
You can then wait for all the jobs to complete and do some post processing of the results.
Here only the length of the website contents are written because the pages themself are lengthy.
# Get the results
# $_.Content.Length gets the length of the content to not spam the output with garbage
$result = Get-RSjob | Receive-RSJob | ForEach { $_.Content.Length }
Write-Host $result

Add One Object from an Array to another Array

I can't figure out what I'm doing wrong and hope someone can point me in the right direction.
I'm trying to iterate through an array of objects and testing on each object and when something is true, I want to take that object and add it to it's own array, as a single object (just like it was in the original array of objects). I seem to be adding the information to the new array, but when I reference the new array by doing newarray[0] it gives me the first item of the object, not the entire object itself.
The issue appears to be with this line:
$global:MachinesInAD += $global:MachineObject
The data in the csv file is a machine hostname, the machines IP address, an error code, and an agent ID.
e.g. MACHINENAME, 10.10.10.10, ERROR101, 123456FF
Function ReadExcelReport (){
$global:Report = "C:\TEMP\Tools\Scripts\agents.csv"
$Unresponsive = import-csv $global:Report | Where-Object {($_.State -eq "QUEUED" -or $_.State -eq "FAILED")} #Add items to array from spreadsheet where the state is equal to queued or failed
$global:UnresponsiveMachineInfo = #()
foreach ($item in $Unresponsive){
$global:UnresponsiveMachineInfo += ,#($item.'Hostname', $item.'IP Address',$item.'Error',$item.'Agent Cert ID') #Build the object - Add the following columns hostname, ip address, error, agent cert id
}
ADCheck
}
Function ADCheck (){
$Machine = $null
$global:MachinesInAD = #()
$global:MachinesNotInAD = #()
$global:MachineObject = New-Object system.object
$global:MachineObject = $Null
$global:MachinesInAD = $null
$global:UnresponsiveMachineInfo | foreach-object { #Iterate through each object in the array
$global:MachineObject = $_
$Machine = $_[0] #Set Machine to the hostname AKA the first element in the array for the current ($_) object (objects defined above)
try{
write-host "Checking A.D. for: $Machine"
if (Get-ADComputer $Machine){ #Check to see if the machine is in A.D.
write-host "Found $Machine in A.D." -ForegroundColor Green
$global:MachinesInAD += $global:MachineObject
}
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { #If the machine was NOT in A.D. catch the error it creates and...
write-warning -message "Machine $Machine not found in A.D."
$global:MachinesNotInAd += $MachineObject
}
}
}
This is happening because what you're calling an object, is just an array (which.. is an object, but your properties are elements, not properties).
Anyway, when you do this:
$global:MachinesInAD += $global:MachineObject
You end up concatenating the arrays.
#(1,2,3) + #(4,5,6)
That results in an array of 6 elements, not 3 numbers and an array.
You should use either a [hashtable] or a [PSObject] instead of an array; or as you did when you built the original one, you'll need to force it into a one elements array, something like:
$global:MachinesInAD += ,#($global:MachineObject)

How to create and populate an array in Powershell based on a dynamic variable?

I've been struggling with this for a couple of days, and I'm not sure how to conquer it. I need to do the following:
Import a csv of users with the following values:
ID, Name, Region
Create an array based on the Region values that I can then use to populate with ID's and Names with that region, ie.
Array_SEA
AA_SCOM, Adam Andrews, SEA
Array_OAK
BB_SCOM, Bob Barker, OAK
Here's the code I've got right now:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#()
foreach ($vitem in $list2)
{
$arraylist += New-Object PsObject -Property #{'Array' = "Array_" + $vitem.bu}
}
foreach ($varray in $arraylist)
{
$arr = new-variable -Name $varray
$arr.value += $varray.array
$arr
}
This produces the following error for records with a duplicate regions:
New-Variable: A variable with name '#{Array=Array_SCA}' already exists.
I'm also getting the following when it tries to add values:
Property 'value' cannot be found on this object; make sure it exists and is settable.
I get that I'm not actually creating arrays in the second section, but I'm not sure how to pass the output of the variable to an array name without turning the variable declaration into the array name, if that makes sense.
I've tried the following with hash tables, and it gets closer:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#{}
foreach ($vitem in $list2){$arraylist[$vitem.bu] = #()}
foreach ($record in $list2)
{
$arraylist[$vitem.bu] += ($record.SCOMID,$record.Name,$record.BU)
Write-host "Array: "
$arraylist[$vitem.bu]
write-host ""
}
The output on this shows no errors, but it just keeps showing the added fields for all of the records for each iteration of the list, so I don't think that it's actually assigning each unique BU to the array name.
I like the hashtable-approach, but I would finetune it a little. Try:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist = #{}
foreach ($vitem in $list2){
if($arraylist.ContainsKey($vitem.BU)) {
#Array exists, add item
$arraylist[($vitem.BU)] += $vitem
} else {
#Array not found, creating it
$arraylist[($vitem.BU)] = #($vitem)
}
}
#TEST: List arrays and number of entries
$arraylist.GetEnumerator() | % {
"Array '$($_.Key)' has $($_.Value.Count) items"
}
You could also use Group-Object like:
$list2 = ipcsv .\TSE_Contact_List.csv | Group-Object BU
#TEST: List groups(regions) and number of entries
$list2 | % {
"Region '$($_.Name)' has $(#($_.Group).Count) items"
}

Resources