Essentially what I'm after is the results of rest API Gateways - Get Datasource Users but retaining the ID (in this example $Line.id from my imported CSV file).
The end result should be a CSV with the following fields -
ID, emailAddress, datasourceAccessRight, displayName, identifier, principalType
I'm new to PowerShell and surprised I got this far but can't figure out this final bit.
Cheers
$webclient=New-Object System.Net.WebClient
$webclient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$Dir = "C:\pbi_pro_user_logs\"
Login-PowerBI
$GateWayFile = Import-CSV -Path "C:\pbi_pro_user_logs\Gateway_Detail.csv"
$Output = #()
foreach ($Line in $GateWayFile){
$Item = $Line.id
$url = "https://api.powerbi.com/v1.0/myorg/gateways/HIDDEN/datasources/"+$Item+"/users"
$Output += (Invoke-PowerBIRestMethod -Url $url -Method Get | ConvertFrom-Json)
}
$Result = $Output.value
$Result | Export-Csv $Dir"GateWay_users.csv" -NoTypeInformation
Try this, using a calculated property from Select-Object:
$GateWayFile = Import-CSV -Path "C:\pbi_pro_user_logs\Gateway_Detail.csv"
$Output = Foreach ($Line in $GateWayFile){
$url = "https://api.powerbi.com/v1.0/myorg/gateways/HIDDEN/datasources/"+$Line.id+"/users"
$Item = (Invoke-PowerBIRestMethod -Url $url -Method Get | ConvertFrom-Json)
# output all properties of the item, plus the ID:
$ItemWithID = $Item | Select *,#{l='Id';e={$line.id}}
Write-Output $ItemWithID
}
# This depends on how you want your csv structured, but for example:
$Result = $Output | Select Id,Value
Or, if Value is a whole object that ID should be assigned inside of, then change the selection lines:
$ItemWithID = $Item.Value | Select *,#{l='Id';e={$line.id}}
$Result = $Output
The following is what I have so far:
$result=#()
$storageaccounts= Get-AzStorageAccount
foreach($storageaccount in $storageaccounts){
$obj = [PSCustomObject]#{
$Name= $storageaccount.ResourceGroupName
$Location= $storageaccount.Location
$Kind= $storageaccount.Kind
$Replication= $storageaccount.sku.Name
}
$result += $obj
}
$result | Export-Csv -Path "insert path"
You were just creating your objects using variables that were not defined.
Also, I believe the property is SkuName not Sku.Name.
When you're not sure what properties your object has, you can use Get-Member:
$storageaccounts = Get-AzStorageAccount
$storageaccounts | Get-Member
$storageaccounts = Get-AzStorageAccount
$result = foreach($storageaccount in $storageaccounts)
{
[PSCustomObject]#{
Name = $storageaccount.ResourceGroupName
Location = $storageaccount.Location
Kind = $storageaccount.Kind
Replication = $storageaccount.SkuName
}
}
$result | Export-Csv -Path "insert path"
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
I am building a multi-step local search engine with powershell that also allows you to email selected pieces of information.
I have got the search engine part down and the email part down, I just need to get the select part down.
So right now, you open the program and it prompts you to search for what you want. If I put in the query when, this is what is returned:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 1/25/2017 3:29 PM 8357890 01 - Kiss Me When I'm Down.mp3
-a--- 1/24/2017 2:15 PM 7189290 09 - When You Love Someone.mp3
Now the goal is to select let's say 01 - Kiss Me When I'm Down.mp3, because I'm then going to put that into an $attachment variable, which will then send the song as an attachment. Is this achievable?
EDIT for clarity:
I've tried working with Select-Object to do this, but I can't get it to allow the user to select the song that they want. That is the goal of this, to allow the user to select the input they want.
This is an inelegant solution that adds an Index NoteProperty using the Add-Member cmdlet. As an example I used Get-ChildItem results:
$Items = Get-ChildItem C:\
$Index = 1
$Count = $Items.Count
foreach ($Item in $Items) {
$Item | Add-Member -MemberType NoteProperty -Name "Index" -Value $Index
$Index++
}
$Items | Select-Object Index, Attributes, LastWriteTime, Name | Out-Host
$Input = Read-Host "Select an item by index (1 to $Count)"
$Selected = $Items[$Input - 1]
Write-Host "You have selected $Selected"
I realise some good answers have been given, but the OP's post got me thinking about extracting the meta data for the MP3s...:
function getMP3Details() {
param ( [System.IO.FileInfo] $mp3file = $null )
[System.__ComObject] $Local:objShell = $null;
[System.__ComObject] $Local:objFolder = $null;
[System.__ComObject] $Local:objFile = $null;
[HashTable] $Local:objTags = #{ 0 = 'Name'; 1 = 'Size'; 13 = 'Artists'; 14 = 'Album'; 15 = 'Year'; 16 = 'Genre'; 20 = 'Authors'; 21 = 'Title'; 28 = 'Bit Rate'; }
[Int32] $Local:intTagIndex = 0;
[String] $Local:strTagName = '';
[String] $Local:strTagValue = '';
[PSCustomObject] $Local:objOutput = $null;
try {
if ( $mp3file -ne $null ) {
$objShell = New-Object -ComObject Shell.Application;
$objFolder = $objShell.NameSpace( $mp3file.DirectoryName );
$objFile = $objFolder.ParseName( $mp3file.Name );
$objOutput = New-Object -TypeName PSCustomObject;
foreach ( $intTagIndex in ($objTags.Keys | Sort-Object) ) {
$strTagName = $objTags[$intTagIndex];
$strTagValue = $objFolder.GetDetailsOf( $objFile, $intTagIndex );
Add-Member -InputObject $objOutput -MemberType NoteProperty -Name $strTagName -Value ( [String] ($strTagValue -replace '[^ -~]', '') );
} #foreach
Write-Output -InputObject $objOutput;
} #if
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
return;
}
[String] $Local:strFolder = '<PATH TO ALBUM>';
[PSCustomObject[]] $Local:arrMP3Tracks = #();
[PSCustomObject] $Local:objSelectedTrack = $null;
try {
Get-ChildItem -LiteralPath $strFolder -File -Filter *.mp3 | Foreach-Object {
$arrMP3Tracks += getMP3Details -mp3file $_;
} #Foreach-Object
$objSelectedTrack = $arrMP3Tracks | Out-GridView -PassThru;
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
exit 0;
I want to create new instance of my custom PSObject. I have a Button object created as PSObject and I want to create new object Button2 which has the same members as Button does, but I can't find a way how to clone the original object without making it referenced in original object (if I change a property in Button2 it changes in Button as well). Is there a way how to do it similarly as with hashtables and arrays via some Clone() method?
Easiest way is to use the Copy Method of a PsObject ==> $o2 = $o1.PsObject.Copy()
$o1 = New-Object -TypeName PsObject -Property #{
Fld1 = 'Fld1';
Fld2 = 'Fld2';
Fld3 = 'Fld3'}
$o2 = $o1.PsObject.Copy()
$o2 | Add-Member -MemberType NoteProperty -Name Fld4 -Value 'Fld4'
$o2.Fld1 = 'Changed_Fld'
$o1 | Format-List
$o2 | Format-List
Output:
Fld3 : Fld3
Fld2 : Fld2
Fld1 : Fld1
Fld3 : Fld3
Fld2 : Fld2
Fld1 : Changed_Fld
Fld4 : Fld4
For some reason PSObject.Copy() doesn't work for all object types. Another solution to create a copy of an object is to convert it to/from Json then save it in a new variable:
$CustomObject1 = [pscustomobject]#{a=1; b=2; c=3; d=4}
$CustomObject2 = $CustomObject1 | ConvertTo-Json -depth 100 | ConvertFrom-Json
$CustomObject2 | add-Member -Name "e" -Value "5" -MemberType noteproperty
$CustomObject1 | Format-List
$CustomObject2 | Format-List
Indeed there is no clone method! However where there is a will...
$o = New-Object PsObject -Property #{ prop1='a' ; prop2='b' }
$o2 = New-Object PsObject
$o.psobject.properties | % {
$o2 | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
}
$o.prop1 = 'newvalue'
$o
$o2
Output:
prop2 prop1
----- -----
b newvalue
b a
Another possibility:
$o1 = New-Object PsObject -Property #{ prop1='a' ; prop2='b' }
$o2 = $o1 | select *
$o2.prop1 = 'newvalue'
$o1.prop1
$o2.prop1
a
newvalue
Here's a [pscustomobject] example with the hidden .psobject.copy():
$a = [pscustomobject]#{message='hi'}
$a.message
hi
$b = $a.psobject.copy()
$b.message
hi
$a.message = 'there'
$a.message
there
$b.message
hi
The Better way i found out was to use ConvertTo-Json and ConvertFrom-Json.
Ee -
Suppose you want to clone a object $toBeClonedObject, just run below code to clone.
$clonedObject = $toBeClonedObject | ConvertTo-Json | ConvertFrom-Json
Starting from PowerShell v5, you can use Class.
The problem with psobject.Copy() is, if you update the cloned object, then your template object's referenced properties will be also updated.
example:
function testTemplates
{
$PSCustomObjectTemplate = New-Object PSCustomObject -Property #{
List1 = [System.Collections.Generic.List[string]]#() # will be updated in template
String1 = "value1" # will not be updated in template
Bool1 = $false # will not be updated in template
}
$objectFromPSTemplate1 = $PSCustomObjectTemplate.psobject.Copy()
$objectFromPSTemplate1.List1.Add("Value")
$objectFromPSTemplate1.String1 = "value2"
$objectFromPSTemplate.Bool1 = $true
# $PSCustomObjectTemplate IS updated, so CANNOT be used as a clean template!
$PSCustomObjectTemplate
Class ClassTemplate {
[System.Collections.Generic.List[string]]$List1 = #() # will not be updated in template
[string]$String1 = "value1" # will not be updated in template
[bool]$Bool1 = $false # will not be updated in template
}
$objectFromClassTemplate = [ClassTemplate]::new()
$objectFromClassTemplate.List1.Add("Value")
$objectFromClassTemplate.String1 = "value2"
$objectFromClassTemplate.Bool1 = $true
# $ClassTemplate IS NOT updated, so can be used as a clean template!
[ClassTemplate]::new()
}
testTemplates
PS C:\Windows\system32> testTemplates
List1 String1 Bool1
----- ------- -----
{Value} value1 False
-> Template from PSCustomObject is updated (referenced property -List1)
List1 String1 Bool1
----- ------- -----
{} value1 False
-> Template from Class is safe
This usually works for me:
$Source = [PSCustomObject]#{ Value = 'Test' };
$Copy = ($Source | ConvertTo-Json) | ConvertFrom-Json;
Put this in a Utility class or define it in your current section
function clone($obj)
{
$newobj = New-Object PsObject
$obj.psobject.Properties | % {Add-Member -MemberType NoteProperty -InputObject $newobj -Name $_.Name -Value $_.Value}
return $newobj
}
Usage:
$clonedobj = clone $obj
Based on the answer by #TeraFlux, here's a function that will do a deep copy on multiple objects and accepts pipeline input.
Note, it leverages json conversion with a default depth of 100, which lends it to a few weaknesses
It's going to be slow on deep or complex objects, or objects with expensive (slow) pseudoproperties (methods pretending to be properties that are calculated on the fly when asked for)
Though it should still be faster than the Add-Member approach because the heavy lifting is going through a compiled function
Anything that can't be stored in JSON may get corrupted or left behind (methods will be a prime candidate for this type of error)
Though any object that can safely go through this process should be savable, able to be safely stored (for recovery) or exported for transportation
I would be interested in any caveats or improvements to deal with these
function Clone-Object {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline)] [object[]]$objects,
[Parameter()] [int] $depth = 100
)
$clones = foreach( $object in $objects ){
$object `
| ConvertTo-Json `
-Compress `
-depth $depth `
| ConvertFrom-Json
}
return $clones
}
Here are some very basic unit tests
$testClone = {
$test1 = $null
$test2 = $null
$test3 = $null
$Test1 = [psCustomObject]#{a=1; b=2; c=3; d=4}
$Test2 = $Test1 | ConvertTo-Json -depth 100 | ConvertFrom-Json
$Test2 | add-Member -Name "e" -Value "5" -MemberType noteproperty
$Test3 = $test2 | Clone-Object
$Test3 | add-Member -Name "f" -Value "6" -MemberType noteproperty
$Test1.a = 7
$Test2.a = 8
#$Expected0 = [psCustomObject]#{a=1; b=2; c=3; d=4}
$Expected1 = [pscustomobject]#{a=7; b=2; c=3; d=4}
$Expected2 = [pscustomobject]#{a=8; b=2; c=3; d=4; e=5}
$Expected3 = [pscustomobject]#{a=1; b=2; c=3; d=4; e=5; f=6}
$results1 = #(); $results1+=$test1; $results1+=$expected1
$results2 = #(); $results2+=$test2; $results2+=$expected2
$results3 = #(); $results3+=$test3; $results3+=$expected3
$results1 | Format-Table # if these don't match then its probably passing references (copy not clone)
$results2 | Format-Table # if these don't match the core approach is incorrect
$results3 | Format-Table # if these don't match the function didn't work
}
&$testClone
Another option:
function Copy-Object($Object) {
$copy = #()
$Object.ForEach({
$currentObject = $_
$currentObjectCopy = New-Object $currentObject.GetType().Name
$currentObjectCopy.psobject.Properties.ForEach({
$_.Value = $currentObject.psobject.Properties[($_.Name)].Value
})
$copy += $currentObjectCopy
})
return $copy
}
Test objects:
class TestObjectA {
[string]$g
[int[]]$h
[string]getJ(){
return 'j'
}
}
class TestObjectB {
[string]$a
[int]$b
[hashtable]$c
[TestObjectA[]]$d
[string]getI(){
return 'i'
}
}
Tests:
$b = New-Object -TypeName TestObjectB -Property #{
a = 'value a'
b = 2
c = #{ e = 'value e'; f = 3 }
d = New-Object -TypeName TestObjectA -Property #{
g = 'value g'
h = #(4,5,6)
}
}
$bCopy = Copy-Object $b
# test with simple comparison
-not $(Compare-Object $b $bCopy)
True
# test json deep conversion output
$bJson = $b | ConvertTo-Json -Depth 10
$bCopyJson = $bCopy | ConvertTo-Json -Depth 10
-not $(Compare-Object $bJson $bCopyJson)
True
# test methods are intact
$bCopy.getI()
i
$bCopy.d.GetJ()
j
# test objects are seperate instances
$bCopy.b = 3
$b.b
2
$bCopy.b
3
Here's my version using Clixml
function Get-PSObjectClone {
param ( [psobject] $InputObject )
$_temp = New-TemporaryFile
$InputObject | Export-Clixml -Path $_temp -Depth 100
$_object = Import-Clixml -Path $_temp
Remove-Item $_temp -Force
Write-Output $_object
}
Works with everything I've thrown at it
Since Select-Object -Property expands wildcards in property names, a simple way to shallow-clone is this:
# Set up object
$o1 = [PSCustomObject]#{
Fld1 = 'Fld1';
Fld2 = 'Fld2';
Fld3 = 'Fld3'}
# Clone
$o2 = $o1 | Select-Object -Property *;
# Tests
$o1 -eq $o2;
$o1 | Format-List;
$o2 | Format-List;