This question already has answers here:
Unexpected ConvertTo-Json results? Answer: it has a default -Depth of 2
(2 answers)
Closed 2 years ago.
My goal was to create a collection of objects (SharePoint sites) with in each object another collection of objects (Lists in that sites). After writing the code below I thought I succeeded but I don't know how to access the Lists objects.
$sites = #()
foreach ($s in $Subsites){
Connect-PnPOnline -Url $s.Url
$Lists = Get-PnPList
$ctx = Get-PnPContext
$web = $ctx.web
$ctx.Load($web)
$ctx.ExecuteQuery()
$listsCollection = #()
foreach ($list in $Lists) {
$props = #{
ListName = $list.Title
ListItems = $list.ItemCount
LastDeletedDate = $list.LastItemDeletedDate
LastModifiedDate = $list.LastItemUserModifiedDate
}
$listObj = New-Object -TypeName PSObject -Property $props
$listsCollection += $listObj
}
$props = #{
SiteName = $s.Title
LastModified = $web.LastItemUserModifiedDate
URL = $web.Url
Lists = $listsCollection
}
$webObj = New-Object -TypeName PSObject -Property $props
$sites += $webObj
}
After running the code I can access the site information like I expected to do
$sites[0].SiteName gives me: "My site name"
And I can see the list information in the object too but it seems to me that it is only string information and not real objects.
$sites[0].Lists gives me:
#{LastDeletedDate=06/12/2019 09:24:57; LastModifiedDate=06/12/2019 09:27:30; ListName=MyList1; ListItems=6}
#{LastDeletedDate=04/19/2019 12:48:14; LastModifiedDate=04/19/2019 12:48:14; ListName=MyList2; ListItems=0}
but I can't acces ListName by using $sites[0].Lists[0].ListName . Get-Member gives me just one property Length. The TypeName of the object is System.String. I tried several other things like using other ways to create a CustomObject and using select -ExpandProperty or Key and Value but no succes either.
Sorry, I didn't include the intermediate steps I used. In that steps I output the $sites object with $sites | ConvertTo-Json | Out-File .\sites.json and later on I import it again with Get-Content .\stes.json | ConvertFrom-Json. After adding the Depth parameter with ConvertToJson -Depth 5 as suggested by AdminOfThings it worked perfectly
Related
I am trying to retrieve a list of User names from Event Viewer log data. The only selector that I saw available was UserID. I would like to convert that to what I see under General > User instead of a SID. I am a PowerShell beginner so I apologize in advance.
This is the script I am using right now. It works great but I would like to add a User name to the columns.
#Server list
$VDI = Get-Content "C:\Scripts\IM\AvayaList.csv"
#Query remote machines
Invoke-Command $VDI {
$Filter = #{
Logname = 'Citrix-VDA-CQI/Admin'
Level = 1,2,3
StartTime = [datetime]::Today.AddDays(-1)
}
Get-WinEvent -FilterHashtable $Filter
} | Select-Object MachineName,TimeCreated,Level,ID,Message | Out-GridView -Title "Results"
I saw this on another post but I am having a hard time integrating what I have with what I found.
Get-WinEvent -MaxEvents 1000 | foreach {
$sid = $_.userid;
if($sid -eq $null) { return; }
$objSID = New-Object System.Security.Principal.SecurityIdentifier($sid);
$objUser = $objSID.Translate([System.Security.Principal.NTAccount]);
Write-Host $objUser.Value;
}
I am using PowerShell v5.1.14393.3866
Thank you!
You could just write a little helper function to resolve the SIDs. Also, just as you used a variable for your filter hashtable, you can use a variable to store the desired properties to make the code easier to read.
Function Resolve-SID ($sid)
{
if($sid)
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($sid)
$objSID.Translate([System.Security.Principal.NTAccount]).value
}
else
{
'N/A'
}
}
$selectprops = "MachineName",
"TimeCreated",
"Level",
"ID",
"Message",
#{n="User";e={Resolve-SID $_.UserID}}
#Server list
$VDI = Get-Content "C:\Scripts\IM\AvayaList.csv"
#Query remote machines
Invoke-Command $VDI {
$Filter = #{
Logname = 'Citrix-VDA-CQI/Admin'
Level = 1,2,3
StartTime = [datetime]::Today.AddDays(-1)
}
Get-WinEvent -FilterHashtable $Filter
} | Select-Object $selectprops | Out-GridView -Title "Results"
I'm running into a problem that isn't really a hindrance, but a possible bug from serialization in extracting job output (guessing?). The code below is a snippet from Start-Job and later captured output by foreach (Start-Job | Receive-Job)
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
# This one below doesn't work.
$return_result = New-Object -TypeName ComputerResult
# This done does work.
$return_result = New-Object -TypeName PSCustomObject -Property #{ComputerName = ""; BLDG = ""; room = ""; organization = ""; user = ""; lastupdate = ""; printerlist = #(); finalprinterlist = #() }
# Here I would start assigning values to $return_result
# Once assigned, return from the Job process to await Receive-Job
return $return_result
The main issue is the $return_result where it works as intended when it's a type PSCustomObject, unlike the ComputerResult class object defined above it. When the script runs, a Get-WmiObject -Class win32_printer -ComputerName $computername Is done to add some WMI objects into my $return_result.printerlist, but when returned, the ComputerResult.printerlist is returning an array of strings - with values of the __PATH property. What should be returned is WMI objects.
PSCustomObject returns just fine with keeping its methods, properties, etc. The ComputerResult.printerlist is kept with full WMI objects.
My assumption is the PSCustomObject is handled differently from the rest of the custom classes, in some way, and perhaps uses a different underlying library when serializing and piping back into the main process.
Why is this? Is this a bug or am I misunderstanding something?
I created a function with your code like this:
Function RetPrinter
{
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
$return_result = New-Object -TypeName ComputerResult
$return_result.PrinterList += gwmi win32_printer
return $return_result
}
I then retrieved my printer list like so:
$Obj = RetPrinter
By piping them to gm, $Obj was returned as TypeName:ComputerResult, and $ObjName.PrinterList was seen as TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer. All seemed to work as expected.
My goal is to use PNP commandlets to set the SharePoint online modern bannerimageurl property for a page.
First I get a list of the pages and their current title and bannerimageurl values
# Get alist of all pages and their banner URLs
$items = Get-PnPListItem -List "SitePages" -Fields ID,Title,BannerImageUrl
$items | %{new-object PSObject -Property #{Id=$_["ID"];Title=$_["Title"];BannerImageUrl=$_["BannerImageUrl"].Url}} | select ID,Title,BannerImageUrl
But even if I then run the following code to set the BannerImage of one page (say ID2)
Set-PnPListItem -List "SitePages" -Id 2 -Values #{"BannerImageUrl" = " https://contoso.sharepoint.com/sites/mycomsite3/bannerimages/bread-braid-tedster-sml.jpg";}
When I run the following again the item 2 shows up as having a changed BannerImageUrl
$items = Get-PnPListItem -List "SitePages" -Fields ID,Title,BannerImageUrl
$items | %{new-object PSObject -Property #{Id=$_["ID"];Title=$_["Title"];BannerImageUrl=$_["BannerImageUrl"].Url}} | select ID,Title,BannerImageUrl
BUT when I actually view the page in the browser that is item 2 there has been no change to the banner image ??
Please tell me what I'm doing wrong when I set the BannerImageUrl.
Your experience and knowledge greatly accepted.
I've written a PS Function for that exact same problem based on the JS Solution here, in case you still need it:
function UpdateBannerImage {
param(
[string]$listName,
[int]$itemId,
[string]$newBannerUrl
)
#get list item
$item = Get-PnPListItem -List $listName -Id $itemId -Fields LayoutWebpartsContent, BannerImageUrl
if($item["LayoutWebpartsContent"] -match 'data-sp-controldata="([^"]+)"'){
# get substring w/ regex
$temp = $item["LayoutWebpartsContent"] | Select-String -Pattern 'data-sp-controldata="([^"]+)"'
$content = $temp.Matches.Groups[1].Value
# replace [] bc sometimes it throws later
$content = $content.replace("[","[").replace("]","]")
# decode
$dec = [System.Web.HttpUtility]::HtmlDecode($content)
# from JSON
$jnContent = ConvertFrom-Json $dec
#set values
if (!$jnContent.serverProcessedContent) {
$jnContent.serverProcessedContent = {};
}
if (!$jnContent.serverProcessedContent.imageSources) {
$jnContent.serverProcessedContent.imageSources = New-Object PSObject;
$jnContent.serverProcessedContent.imageSources | add-member Noteproperty imageSource $newBannerUrl
}
if(!$jnContent.serverProcessedContent.imageSources.imageSource){
$jnContent.serverProcessedContent.imageSources | add-member Noteproperty imageSource $newBannerUrl
}
$jnContent.serverProcessedContent.imageSources.imageSource = $newBannerUrl
# need to always create new properties, otherwise nothing changes
$curTitle = "";
if($jnContent.properties){
$curTitle = $jnContent.properties.title;
}
$jnContent.properties = New-Object PSObject;
$jnContent.properties | add-member Noteproperty title $curTitle
$jnContent.properties | add-member Noteproperty imageSourceType 2
# to JSON
$newContent = $jnContent | ConvertTo-Json -Compress
$enc = [System.Web.HttpUtility]::HtmlEncode($newContent)
$enc = $enc.replace("{","{").replace(":",":").replace("}","}").replace("[","[").replace("]","]")
# replace full item property
$fullContent = $item["LayoutWebpartsContent"].replace("[","[").replace("]","]");
$fullContent = $fullContent -replace $content, $enc
$fullContent.replace("[","[").replace("]","]")
# set & update
$item["LayoutWebpartsContent"] = $fullContent
$item.Update()
# not really sure if needed, but also update bannerURL
Set-PnPListItem -List $listName -Id $itemId -Values #{"BannerImageUrl" = $newBannerUrl; }
}
}
new here sorry if i mess up the format, also uploaded for safety here :P
I'm trying to import an excel sheet and it works fine. However, the order of the columns is not kept. For example text that is written in the first column is being output as the second Property instead of the first.
Example input file:
ServerName | First name | Last name
C:\Drive | Bob | Johnson
The example above gives the following output as PSObject, which is incorrect as it's not in the same order as the file above is. It should start with the ServerName first, instead of:
First name : Bob
ServerName : C:\Drive
Last name : Johnson
The code:
Function Import-Excel {
[CmdletBinding()]
Param (
[String]$FileName,
[String]$WorksheetName
)
Process {
$OleDbConnection = New-Object 'System.Data.OleDb.OleDbConnection'
$OleDbCommand = New-Object 'System.Data.OleDb.OleDbCommand'
$OleDbAdapter = New-Object 'System.Data.OleDb.OleDbDataAdapter'
$DataTable = New-Object 'System.Data.DataTable'
$ConnString = 'Provider=Microsoft.ACE.OLEDB.12.0;Data Source='
$ConnString += $FileName
$ConnString += ';Extended Properties="Excel 12.0;HDR=YES;IMEX=1";'
$OleDbConnection.ConnectionString = $ConnString
$OleDbConnection.Open()
$OleDbCommand.Connection = $OleDbConnection
$OleDbCommand.CommandText = "SELECT * FROM [$WorksheetName$]"
$OleDbAdapter.SelectCommand = $OleDbCommand
$OleDbAdapter.Fill($DataTable) | Out-Null
$OleDbConnection.Close()
$columnArray = #()
foreach ($Col in $DataTable.Columns.ColumnName) {
$ColumnArray += $Col.toString()
}
$returnObject = #()
foreach ($Ro in $DataTable.Rows) {
$i=0;
$rowObject = #{}
foreach ($colu in $Ro.ItemArray) {
$rowObject += #{$columnArray[$i]=$colu.toString()}
$i++
}
$returnObject += New-Object PSObject -Property $rowObject
}
return $returnObject
}
}
I've tried so far by using an ArrayList instead. This works fine to keep the order of the input correct, but New-Object PSObject does not accept an ArrayList as input.
Thank you for your help.
Found my answer here.
It appears to be possible to have an Ordered hashtable:
$rowObject = [Ordered]#{}
The script works with no issue but instead of out put i want to use custom object
####################
# Get AD Site List #
####################
Write-Verbose “Get AD Site List `r”
[array] $ADSites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
$ADSitesCount = $ADSites.Count
Write-Output “There are $ADSitesCount AD Sites `r”
ForEach ($Site in $ADSites)
{ ## OPEN ForEach Site in ADSites
$SiteName = $Site.Name
$SiteSubnets = $Site.Subnets
Write-Output “Site Name: $SiteName `r”
Write-Output “Site Servers: $SiteServers `r”
Write-Output ” `r”
}
Updated Script:
#################### # Get AD Site List # ####################
Write-Verbose “Get AD Site List r”
$ADSites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
#$adsites= #{}
$ADSitesCount = $ADSites.Count
Write-Output “There are $ADSitesCount AD Sites r”
$properties = #{'SiteName'=$ADSites.Name; 'SiteServers'=$ADSites.server; }
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object
The problem lies in this line:
$properties = #{'SiteName'=$ADSites.Name; 'SiteServers'=$ADSites.server; }
$ADSites is an array. It contains all sitenames so what you will create looks like this:
Sites
----
{a, b, c}
The solution is to create an array that holds multiple objects and add one object per site:
$collection= #()
foreach($site in $ADSites){
$properties = #{ "Name" =$site.Name; "SiteServers"=$site.Server}
$collection +=(New-Object –TypeName PSObject –Prop $properties )
}
$collection
Write-Output is used to output to the pipeline not for producing screen output
Not sure if you just might be over thinking this but this will give you the same information that you are going for in a table format simply using Select-Object to get only the properties you want
$ADSites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
Write-Output “There are $($ADSites.Count) AD Sites `r”
$ADSites | Select-Object name,servers
Obfuscated output
There are 4 AD Sites
Name Servers
---- -------
SITE1 {SERVER1.DOMAIN.NET, SERVER2.DOMAIN.NET}
STE345 {SERVER3.DOMAIN.NET}
LOCCOMP {SERVER4.DOMAIN.NET}
THING {SERVER9720.DOMAIN.NET, SERVER7.DOMAIN.NET}