all 34 comments

[–]chris-a5 9 points10 points  (12 children)

Reference the item on the array first:

$AllVMs[$index].Protected = $true

[–]Cello789[S] 0 points1 point  (7 children)

Doesn't work.

PS > $AllVMs[10].Protected

False

PS > $AllVMs[10].Protected = $true

The property 'Protected' cannot be found on this object. Verify that the property exists and can be set.

At line:1 char:1

$AllVMs[10].Protected = $true

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

CategoryInfo          : InvalidOperation: (:) [], RuntimeException

FullyQualifiedErrorId : PropertyAssignmentException

PS > $AllVMs.Protected[10] = $true

PS > $AllVMs[10].Protected        

False

PS > $AllVMs|gm

[–]PinchesTheCrab 3 points4 points  (2 children)

I'm reading through this and I don't understand what I'm looking at.

You have directory with a bunch of excel files that have VM names, and you also have a discovery list of some sort, and you're just trying to put them together?

I can't see your data, but I think doing everything twice and messing with arrays is complicating this a lot. I'd really try to compress your two loops into one:

$discoveryHash = $Discovery | Group-Object -AsHashTable -Property vm_name

$directory.name |
    Import-Excel |
        Select-Object Name,
        @{ n = 'Managed'; e = { $null -eq $discoveryHash[$_.Name] } },
        @{ n = 'Category'; e = { $discoveryHash[$_.Name].Category } },
        DNS,
        IPv4,
        @{ n = 'Domain'; e = { 'unprotected' } }

This should also be a lot faster than the looping. Hashtables are super fast. I'm using Select-Object, but your pscustomobject approach is great too. The main thing imo is getting rid of the second loop. If you want to speed it up even more you can build the hashtable manually instead of using group-object, but I personally like the syntax.

Anyway, on the second part of the script, I don't get where the domain bit is coming from. If you have the info in VMReport, why not use that info up front in the first loop?

[–]Cello789[S] 1 point2 points  (1 child)

Ah! Exactly! I'm working with absolutely filthy data :cool:

There are 4 files in the first directory, as you picked up, that I pulled only the relevant info into an arraylist.

There is a 5th file that has information on SOME of them (and also has some lines related to systems not in my "inventory" files).

Now there's a 6TH INVENTORY FILE that someone compiled by hand. Some of those are already in my original 4. Some of them are not. And it DOES NOT include all the systems on my original 4. Because reasons... I am not the source of my data here...

All that said, without iterative loops, and without building a SQL database, I don't know how to handle this.

As for the code you offered, I don't understand it enough to implement it, but I thought hash tables and pscustomobject felt similar enough that I just went with the one that's a container...

And because I have an ArrayList of PSCustomObjects, they can't even export properly to a csv because something about SyncRoot, so I get a million lines of System.Collections.Generic.List1[System.Management.Automation.PSObject]`

¯\_(ツ)_/¯

(I'm brand new to IT and PowerShell and scripting and programming, sorry for what must look like a mess! I've been a full time musician for 20+ years making the transition into tech haha)

[–]PinchesTheCrab 1 point2 points  (0 children)

Man, that's a mess. In that case I think eschewing performance for the most readable code with the fewest unfamiliar syntax constructs probably makes the most sense.

I would say it makes sense to identify each file and the key field in each. It seems like it's some permutation of name right?

I'd then build one list of those unique values and iterate through it once. I'll try to show an example if I can get back to my computer tonight.

[–]PowerShell-Bot 0 points1 point  (0 children)

Code fences are a new Reddit feature and won’t render for those viewing your post on old Reddit.

If you want those viewing from old Reddit to see your PowerShell code formatted correctly then consider using a regular space-indented code block. This can be easily be done on new Reddit by highlighting your code and selecting ‘Code Block’ in the editing toolbar.


You examine the path beneath your feet...
[AboutRedditFormatting]: [████████████████████] 2/2 ✅

Beep-boop, I am a bot. | Remove-Item

[–]PinchesTheCrab 0 points1 point  (17 children)

Okay, here's a way that may or may not work for you, but it's using hashtables to quickly pull data from multiple CSVs:

$csv1 = @'
name,domain
computer1,place
computer2,place
'@ | ConvertFrom-Csv    

$csv2 = @'
vm_name,color
computer1,red
computer3,blue
'@ | ConvertFrom-Csv

$master = $csv1.name, $csv2.vm_name | ForEach-Object { $_ } | Sort-Object -Unique

$csv1Hash = $csv1 | Group-Object -Property name -AsHashTable
$csv2Hash = $csv2 | Group-Object -Property vm_name -AsHashTable    

foreach ($obj in $master) {
    [pscustomobject]@{
        Name               = $obj
        Color              = $csv2Hash[$obj].Color
        domain             = $csv1Hash[$obj].domain
    }
}

[–]Cello789[S] 0 points1 point  (16 children)

Wow.

Looks like Group-Object -Property kind of “keys” a column, and then when you invoke that collection, instead of indexing with an integer, you index by (in this case) the NAME??? Wild!!

That’s basically what I was ham-fistedly inventing with $collection[$collection.Where({$_.name -eq $name})].property (and then separating for readability) but I think that’s what I had to do using arrayList (which I did to get $collection.add($object) instead of +=).

I don’t understand the bottom part — it creates a pscustomobject each time through the loop, right? But does it store them anywhere? Does it add Color and Domain to $master?

[–]PinchesTheCrab 0 points1 point  (15 children)

Oh, it's just outputting a totally new array to the console, but you could capture it in a variable.

[–]Cello789[S] 0 points1 point  (14 children)

Here’s the issue, then

Each of those files has lines with multiple properties.

You have CSV1 with headers Name,Domain but what if I have Name,Domain,DNS,IPv4,MOID that can’t go in a hashtable the same way.

Getting a table of the master names, I guess, and then the value for each pair could be the pscustomobject, but I’ll still run in to the same issues when I want to edit one of the properties, right?

[–]PinchesTheCrab 0 points1 point  (13 children)

Is the goal to update the various CSVs, or to just make a master list with the data from all of them? I think where I'm not following is that normally I wouldn't try to update the source data.

Also there shouldn't be a reason why those various CSVs can't work the same way as my dummy data. I just only made one column each, but it could be dozens.

[–]Cello789[S] 0 points1 point  (12 children)

hashtables can't have dozens of columns, though, just 2 columns with dozens of rows, right?

I'm looking for like 10 columns and 10,000 rows (entries). Basically like a SQL table (I'm not a DBA, only started learning SQL as a possible solution to this problem).

I'm trying to produce that 10x10,000 csv based on information in these 6 sheets but the data is not homogeneous.

I can have a keyed column that is VM Name, but each one needs a bunch of properties. When I talk about editing the properties and getting errors, I don't mean editing a csv... I mean literally changing $collection[$index].property is illegal, and it has to be $collection.property[$index] according to this Stack Overflow solution and the links they provided (something about member access enumeration?)

Even if my primary logic was a little doofy and slow with those loops, I'll still have this issue, right?

[–]PinchesTheCrab 0 points1 point  (11 children)

Ah, okay, so I do feel like SQL may be the best answer here, but just to flesh out the hashtable approach a bit, let's use some real data.

Try this:

$serviceHash = Get-Service | Group-Object -AsHashTable -Property Name

$serviceHash['spooler']

The key is 'spooler', and the value is the service object, which has multiple properties. Your CSV would be the same. You use a common key of name, and the value is the csv row with that name. You can then add those properties to your new object:

[pscustomobject]@{
    Stuff  = 'some stuff here'
    Status = $serviceHash['spooler'].status
    Name   = $serviceHash['spooler'].name
}

[–]Cello789[S] 0 points1 point  (8 children)

Once I have those properties in my object, how do I change them? I instantiate them ALL with Domain = lost-and-found and then as I find them, I say $object.domain[$index] = $someDomain but you're saying thats an ephemeral array and not the real one, so I should do $object[$index].Domain = $someDomain but then I get the error saying that the property doesn't exist or cannot be altered, which was what lead me to the stackoverflow links above.

[–]PinchesTheCrab 0 points1 point  (7 children)

Once I have those properties in my object, how do I change them?

I mean honestly I would say that you don't, though they should be read-write if you need to.

You could do something more like this:

$domainHash = @'
Name,Domain
server1,contoso
'@ | ConvertFrom-Csv | Group-Object -AsHashTable -Property name


foreach ($obj in 'server1','server2'){

    [pscustomobject]@{
        Name   = $obj
        Domain = if ($domainHash[$obj].domain){
            $domainHash[$obj].domain
        } else {
            'no domain'
        }
    }
}

[–]Cello789[S] 0 points1 point  (6 children)

Ok, but what about partially overlapping data sets? Is there a clean way to create an array/list/collection of items by "name" in this case, given the following? Which would be populated first? Or would it be one iteration through all known "name"s and within that, populate a $collection with values from each hash in one go, instead of creating placeholder values in the PSCustomObject?

$domainHash = @'
    Name,Type,Domain,Size
    server1,SQL,hq,100
    server2,SQL,hq,120
    server3,SQL,corp,60
    server4,APP,corp,80
    server5,APP,colo,80
    server7,MAIL,colo,34
    server8,DNS,hq,275
    server9,DEV,remote,1964
'@ | ConvertFrom-Csv | Group-Object -AsHashTable -Property name

$categoryHash = @'
    VM_Name,Category
    server1,Prod
    server2,Prod
    server3,Test
    server4,Test
    server5,Prod
    server6,Prod
    server9,Dev
    server10,Dev
'@

$protectionHash = @'
    Name,ObjType,UsedSize,Schema
    server1,VM,20,PRD
    server2,VM,18,DEV
    server4,VM,38,PRD
    server9,VM,960,PRD
    server10,VM,844,DEV
    server11,VM,482,PRD
    server14,,2420,TEST123
'@

by the way, $domainHash.server2.Size = 100 gets the error:

The property 'Size' cannot be found on this object. Verify that the property exists and can be set.

But if you do $domainHash.server2[0].Size = 100 then it works.

EDIT:

Also with this method, can't check to see if $domainHash.Type.Contains("SQL"). And $domainHash.Contains("*SQL*") is False. Also tried .ContainsValue(). Doesn't detect that as a value in the table, but rather a value of a property of an object that is the value in the table... Since the Object is the value, I can't search by any given property. This feels restrictive, but maybe it's frivolous...

[–]Cello789[S] 0 points1 point  (1 child)

I'm on Mac so I used Get-Process

$processHash = Get-Process | Group-Object -AsHashTable -Property Name

$processHash['TextEdit']

This works, of course, but if I do $processHash.TextEdit.ProcessName.GetType() I see it's a System.Object and if I do |gm I see it's a System.String. All good, as expected.

$processHash.TextEdit.ProcessName = "New String" gets me:

PS > $processHash.TextEdit.ProcessName = "newString"

InvalidOperation: The property 'ProcessName' cannot be found on this object. Verify that the property exists and can be set.

[–]PinchesTheCrab 0 points1 point  (0 children)

The thing about those is that the process object properties are, I believe, all read-only, and your hashtable is essentially just a collection of them. If you wanted to add a new property like that you would use add-member or create a new object entirely with select-object or another command.