How to make a PSCustomObject with Nested Values - powershell

I am trying to create a PSCustomObject with nested values and am having a really tough time, I see plenty of examples of hash tables and pscustom objects but for only extremely basic sets of data.
I am trying to create a a pscustom object that will have a server name property and then another property that has an array of services as well as their current running status.
I was able to make a hash table of the services and their running status but when I try to make an object with the has table it doesnt work very well:
hash table contains service names and their running status (stopped or running)
$hashtable
$myObject = [PSCustomObject]#{
Name = "$server"
Services = "$hashtable"
}
I am open to anything really, I have plenty of examples of how to convert items from JSON or XML and would love be able to use those as well but still having the same issue with being able to format the data in the first place.
edit: sorry about some of the vagueness of this post. As some people have already mentioned, the problem was the double qoutes around the hashtable. Everything is working now

As #t1meless noted in the comments, when you enclose a variable in double quotes it will attempt to convert that value to a string. For a Hashtable object, rather than give any information on from the object this will return "System.Collections.Hashtable". If you remove the double quotes, it will store the value of the hashtable as you are intending.
Here is a full example of what pulling the service information from a server and storing the values in a custom object. Note that $server can still be left in quotes as it is a string, but since it's already a string this would be unnecessary.
$myObject = Foreach ($Server in $Servers) {
$hashtable = #{}
Get-Service -ComputerName $Server | ForEach-Object { $hashtable.Add($_.name,$_.Status}
[PSCustomObject]#{
Name = "$Server"
Services = $hashtable
}
}

Related

How to use Remove-LocalGroupMember to remove from a server?

I have the following script that is supposed to remove members from a server:
$ssasInstance = ""
$accountName= ""
Import-Module SqlServer
[Microsoft.AnalysisServices.Server]$SSASserver = New-Object ([Microsoft.AnalysisServices.Server])
$SSASserver.Connect($ssasInstance)
$role= $SSASserver.Roles["Administrators"]
$role.Members.Remove($accountName)
$role.Update()
The problem is for some reason Remove() is not really working, no errors generated, but it doesnt remove the user.
I tested the script by instead adding a user, $role.Members.Add($accountName) and this works great! so i know that it must be a bug with the remove() method, and the only option I have is to use Remove-LocalGroupMember
I tried just using it like this:
$ssasInstance = ""
$accountName= ""
Import-Module SqlServer
[Microsoft.AnalysisServices.Server]$SSASserver = New-Object ([Microsoft.AnalysisServices.Server])
$SSASserver.Connect($ssasInstance)
$role= $SSASserver.Roles["Administrators"]
Remove-LocalGroupMember -Group "Administrators" -Member "$accountName"
$role.Update()
but that doesnt work either...although i think its because it doesnt know exactly where its removing from...
I tried this too, but to no avail:
Remove-LocalGroupMember -Group "$role" -Member "$accountName"
So how can i integrate this module into my script?
This is an unfortunate confluence of circumstances: when you do
$role.Members.Add("member")
This works because, under water, the string "member" is implicitly converted by PowerShell to a RoleMember with a Name of member and an empty Sid. All fine. However, if you then do
$role.Members.Remove("member")
Nothing happens, because you will create a new RoleMember instance, and since RoleMember has no implementation of .Equals() (a fairly bizarre oversight), different instances will never be considered the same.
This means you can only remove members by getting the actual instance in the collection (or by index, I suppose):
$member = $role.Members.Where{$_.Name -eq "member"} | Select -First 1
$role.Members.Remove($member)
Note that you will not get an error if there is no such member (because Remove allows $null, again in a rather bizarre oversight), so you may want to check for $member -eq $null if you want to verify that.
Definitely do not use Remove-LocalGroupMember -- that cmdlet is part of a completely different module and removes members from local Windows groups, not SSAS roles.

Why is Enumeration Name property not working?

I have a script that i want to display the connection strings of a database
Import-Module SqlServer
$AS = New-Object Microsoft.AnalysisServices.Server
$AS.connect("server1")
Now if i use the FindByName() property
$db = $AS.Databases.FindByName("database1")
$db.DataSources[0].ConnectionString
I get back the connection string successfully
however if i use Enumerator
foreach ($db in $AS.Databases.GetEnumerator())
{ $dbName = $db.Name
$dbName
$dbName.DataSources[0].ConnectionString
}
I get back the database name along with error/exception (because it couldnt get connection string for some reason):
database1
Cannot index into a null array.
database2
Cannot index into a null array.
I tried the following also:
$database1 = "database1"
$database1.DataSources[0].ConnectionString
and i also get back the same exception
So why is it that only FindByName works?
for additional info, this is what GetEnumerator lists:
$AS.Databases.GetEnumerator()
but also $AS.Databases
outputs the same thing...so whats even the point of the enumerator?
gm -i $AS.Databases
gm -i $AS.Databases..GetEnumerator()
Part of what you're seeing is PowerShell's handling of (some) enumerables. Many (most?) are unrolled by PowerShell automatically, so the call to .GetEnumerator() isn't needed.
That's what's happening in your last example, looking at $AS.Databases vs $AS.Databases.GetEnumerator(). But it's only because you sent it out to the pipeline in that case; it's the display process that did the unrolling (in both cases).
If you did gm -i $AS.Databases vs gm -i $AS.Databases.GetEnumerator() you're going to see the difference; same if you assigned each of those to a variable and tried to call methods on them.
But back to using foreach it should again be redundant: foreach ($db in $AS.Databases) should work the same as foreach ($db in $AS.Databases.GetEnumerator()) but I don't have this type in my env right now to test that.
So back to the issue at hand inside the foreach, I suggest you start checking types again. Compare:
$db = $Analysis_Server.Databases.FindByName("database1")
gm -i $db
to
foreach ($db in $AS.Databases.GetEnumerator())
{
gm -i $db
break
}
You might find the types aren't what you think.
This is especially true because you're using dot . notation, because PowerShell has another array shortcut built-in since version 3, whereby you can use . on an array of types, to return an array of the .Property of each item. For example:
$p = Get-Process chrome # choose your own adventure
$p.Count
$p[0].Name
$p.Name
So a property you thought you were accessing on a single object, may have been on an array of objects, and may have been returning an array (or a single object), and handing that in the foreach may have returned a different quantity, or something, resulting in your attempt to index into what used to be an array, no longer work.
But again it's speculation on my part because I don't have those objects. Hopefully this helps you dig deeper into it though.
PowerShell does its own enumerating.
This done the trick!
foreach ($db in $AS.Databases){
Write-Hst $db.Name -Fore green
$db.DataSources | ForEach-Object{$_.ConnectionString}
}

How do i return compatibility level of just one database?

I referenced this page: https://technet.microsoft.com/en-us/hh213141(v=sql.100)
Import-Module SqlServer
$as = New-Object Microsoft.AnalysisServices.Server
$as.connect("$Server")
$as.databases
Write-Host "Compatibility level ="$as.DefaultCompatibilityLevel
but this returns ALL databases back...
I want to specify just one database to get the compatibility level of...
I tried this,
$as.databases["$Database"]
the DB i am interested in has lvl 1103,
but it seems not to return the proper level, because its returning 1200...
I am not familiar with this API, but I see that the $as.Databases property is of type DatabaseCollection. The DatabaseCollection class does have indexers that take either an Int32 or a String, like you're attempting to use with $as.databases["$Database"].
Note that the documentation for the String indexer says the parameter is an "identifier" for the database to be returned. Also note that the Database class has separate properties for ID and Name, so there is a distinction between the two. So, my suggestion is, make sure you are passing the ID, not the name, when you try to retrieve a Database instance that way.
Alternatively, if you do want to search by name you could use the FindByName method...
$as.Databases.FindByName($Database)
...or GetByName method...
$as.Databases.GetByName($Database)
...with the difference being that the former returns $null if no such database exists and the latter throws an exception.
If all else fails you could retrieve the desired database like this...
$as.Databases | Where-Object { $_.ID -eq $Database }
...or like this...
$as.Databases | Where-Object { $_.Name -eq $Database }
...depending on which property corresponds to the value in $Database.
Finally, in your code you are attempting to access a DefaultCompatibilityLevel property, which I don't see defined in the Database class. There is, however, a CompatibilityLevel property.

returning and referencing remote powershell variable results

I'm very new to powershell so looking some assistance. I am trying to run remote powershell script to check health of or VDI enviroment using Citrix Commandlets. (I am implementing the script on Microsoft orchestrator .Net Activity). So I have the following code:
#2012 VDI Desktop check
$vdims = "MyCitrixPowershellSDKServer"
function vdidesktopcheck
{
asnp Citrix*
$result = Get-BrokerDesktop -InMaintenanceMode $True -DesktopGroupName Unidesk
$result
}
$output = invoke-command -computer $vdims -scriptblock ${function:vdidesktopcheck}
$machinename = $output.machinename
$machinename
$state = $output.RegistrationState
$state
So when I use orchestrator to expose the variables $machinename, $state - I get the 'last' result from the involked Get-BrokerDesktop query. However Get-Brokerdesktop query may have multiple machines returned so I am hoping to be able to reference each of the machines that match the query output. Thats the basic requirement - what I am hoping to be able to do is further refine the basic Get-BrokerDesktop query to be able to count the number on machines output to say > 3 (ie more than 3 machines in MaintMode is unacceptable) and also check that the MachineName property is not equal to a particular value, ie the 3 test machine names in the environment which may be expected to be in MaintenanceMode.
Hope this makes sense, if not I'll try and elaborate. Any help much appreciated!!
One of the limitations of Orchestrator is that you can only pass strings across the data bus, and you need to pass an array of objects. You need to serialize the object array to a string. One way to do that is to convert the array to json, then use convertfrom-json when you get it back to get an object array to work with.
Don't have a SCORCH server handy to test with, so this isn't tested at all.
$vdims = "MyCitrixPowershellSDKServer"
function vdidesktopcheck
{
asnp Citrix*
$result = Get-BrokerDesktop -InMaintenanceMode $True -DesktopGroupName Unidesk
$result
}
$output = invoke-command -computer $vdims -scriptblock ${function:vdidesktopcheck} |
select machinename,RegistrationState | ConvertTo-Json
$Output

Export-csv formatting. Powershell

Let's start off by saying that I'm quite new to Powershell and not the greatest one working with it's code and scripts but trying to learn. And now to the problem!
I'm working on a script that fetches information from computers in the network. I've got some code that works quite well for my purposes. But I'm having some problem when it comes to some information, mostly information that contains multiple objects, like service.
#This application will pull information from a list of devices. The devices list is
sent with C:\Users\test.txt and output is pushed to a file C:\Users\devices.csv
function kopiera
{
param
(
[Parameter(ValueFromPipeline=$true)]
[string[]]$Computer=$env:computername
)
Process
{
$computer | ForEach-Object{
$service=Get-WmiObject -Class Win32_Service -Computername $_
$prop= [ordered]#{
Service =$service.caption
}
New-Object PSCustomObject -Property $prop
}
}
}
Get-Content C:\Users\test.txt | kopiera | Export-csv c:\Users\devices.csv
When I export the csv file it looks like this:
TYPE System.Management.Automation.PSCustomObject
"Service"
"System.Object[]"
So it doesn't fetch the service.caption (Because there are too many?).
But If I replace the export-csv C:\Users\device.csv with out-file C:\Users\devices.txt it looks like this instead:
{Adobe Acrobat Update Service, Adobe Flash Player Update Service, Andrea ADI Filters Service, Application Experience...}
So it's starting to look better, but it doesn't get them all (Still because there are too many services?). What I'd like to do with this export/out-file is to get the information to appear vertically instead of horizontal.
(Wanted result)
Adobe Acrobat Service
Adobe Flash Player Update Service
and so on..
instead of:
(Actual result)
Adobe Acrobat Update Service, Adobe Flash Player Update Service, and so on...
Is there a way to make this possible, been trying for a while and can't wrap my brain around this.
Any help is appreciated!
CSV is not usually a good choice for exporting objects that contain multi-valued or complex properties. The object properties are going to be converted to a single string value. The only way to store an array of values is to convert it to a delimited string.
$prop= [ordered]#{
Service =($service.caption) -join ';'
}
will create a semi-colon delimited string, and you'll have to deal with splitting it back out in whatever appication is using the csv later.
If you want to save and re-import the original object with the property as an array, you can switch to Export-CLIXML instead of Export-CSV.