I've got a list of files in an array. I want to enumerate those files, and remove specific files from it. Obviously I can't remove items from an array, so I want to use an ArrayList.
But the following doesn't work for me:
$temp = Get-ResourceFiles
$resourceFiles = New-Object System.Collections.ArrayList($temp)
Where $temp is an Array.
How can I achieve that?
I can't get that constructor to work either. This however seems to work:
# $temp = Get-ResourceFiles
$resourceFiles = New-Object System.Collections.ArrayList($null)
$resourceFiles.AddRange($temp)
You can also pass an integer in the constructor to set an initial capacity.
What do you mean when you say you want to enumerate the files? Why can't you just filter the wanted values into a fresh array?
Edit:
It seems that you can use the array constructor like this:
$resourceFiles = New-Object System.Collections.ArrayList(,$someArray)
Note the comma. I believe what is happening is that when you call a .NET method, you always pass parameters as an array. PowerShell unpacks that array and passes it to the method as separate parameters. In this case, we don't want PowerShell to unpack the array; we want to pass the array as a single unit. Now, the comma operator creates arrays. So PowerShell unpacks the array, then we create the array again with the comma operator. I think that is what is going on.
Probably the shortest version:
[System.Collections.ArrayList]$someArray
It is also faster because it does not call relatively expensive New-Object.
Related
I have an issue rearding the passing of more than one array parameter. I was able to do a "for each" cycle to execute my array parameter "SPPATH", but unfortunately I can pass only one, here is my code:
{"SPPATH":"#{item()}","SPFOLPATH":"#{pipeline().parameters.SPFOLPATH}","updsppath":"#{pipeline().parameters.updsppath}","Storageacct":"#{pipeline().parameters.Storageacct}","sapath":"#{pipeline().parameters.sapath}","saoppath":"#{pipeline().parameters.saoppath}"}
I want to pass "updsppath" also in the array because my output is on different locations, is it possible to do that, if so, how?
thanks in advance
I have reproduced the above and able to iterate multiple arrays inside ForEach.
For this the length of the all arrays should be same.
Use another array for indexes of these.
For Sample I have two array parameters like below.
I have created another array for index like below.
#range(0,length(pipeline().parameters.arr1))
Give this index_array to ForEach.
Create a res array variable in pipeline and inside ForEach, use append variable with the below dynamic content.
#json(concat('{"arr1":"',pipeline().parameters.arr1[item()],'","SPFOLPATH":"',pipeline().parameters.arr2[item()],'"}'))
After ForEach if you look at variable result (for showing here I have assigned to another variable), it will give you the desired JSON.
Result:
You can use this procedure to generate the desired array of objects and pass it to the logic apps as per your requirement.
Given a properly defined variable
$test = New-Object System.Collections.ArrayList
.Add pollutes the pipeline with the count of items in the array, while .AddRange does not.
$test.Add('Single') will dump the count to the console. $test.AddRange(#('Single2')) will be clean with no extra effort. Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Given that .AddRange requires coercing to an array when not using a variable (that is already an array) I am tending towards using [void]$variable.Add('String') when I know I need to only add one item, and [void]$test.AddRange($variable) when I am adding an array to an array, even when $variable only contains, or could only contain, a single item. The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Because many years ago, someone decided that's how ArrayList should behave!
Add() returns the index at which the argument was inserted into the list, which may indeed be useful and makes sense.
With AddRange() on the other hand, it's not immediately clear why it should return anything, and if yes, what? The index of the first item in the input arguments? The last? Or should it return a variable-sized array with all the insert indices? That would be awkward! So whoever implemented ArrayList decided not to return anything at all.
In C# or VB.NET, for which ArrayList was initially designed, "polluting the pipeline" doesn't really exist as a concept, the runtime would simply omit copying the return value back to the caller if someone invokes .Add() without assigning to a variable.
The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
No, it's completely unnecessary. AddRange() is not magically one day gonna change to output anything.
If you don't ever need to know the insert index, use a [System.Collections.Generic.List[psobject]] instead:
$list = [System.Collections.Generic.List[psobject]]::new()
# this won't return anything, no need for `[void]`
$list.Add(123)
If for some reason you must use an ArrayList, you can "silence" it by overriding the Add() method:
function New-SilentArrayList {
# Create a new ArrayList
$newList = [System.Collections.ArrayList]::new()
# Create a new `Add()` method, then return the list
$newAdd = #{
InputObject = $newList
MemberType = 'ScriptMethod'
Name = 'Add'
Value = {param($obj) $this.AddRange(#($obj))}
}
Write-Output $(
Add-Member #newAdd -Force -PassThru
) -NoEnumerate
}
Now your ArrayList's Add() will never make a peep again!
PS C:\> $list = New-SilentArrayList
PS C:\> $list.Add(123)
PS C:\> $list
123
Apparently I didn't quiet understand where you where heading to.
"Add pollutes the pipeline", at a second thought is a correct statement but .Net methods like $variable.Add('String') do not use the PowerShell pipeline by itself (until the moment you output the array using the Write-Output command which is the default command if you do not assign it to a variable).
The Write-Output cmdlet is typically used in scripts to display
strings and other objects on the console. However, because the default
behavior is to display the objects at the end of a pipeline, it is
generally not necessary to use the cmdlet.
The point is that Add method of ArrayList returns a [Int32] "The ArrayList index at which the value has been added" and the AddRange doesn't return anything. Meaning if you don't assign the results to something else (which includes $Null = $test.Add('Single')) it will indeed be output to the PowerShell Pipeline.
Instead you might also consider to use the Add method of the List class which also doesn't return anything, see also: ArrayList vs List<> in C#.
But in general, I recommend to use native PowerShell commands that do use the Pipeline
(I can't give you a good example as it is not clear what output you expect but I noticed another question you removed and from that question, I presume that this Why should I avoid using the increase assignment operator (+=) to create a collection answer might help you further)
Suppose I have a list:
$DeletedUsers = New-Object System.Collections.Generic.List[System.object]
So I can easily add and remove users from the collection.
I want to be able to pass this list to a function that does something, but without modifying the original list, and it must stay of the same generic list type.
convertAll() seems to do exactly what I want without having to script out the creation of a new list myself with foreach-object, but I don't understand how to utilize the overload definitions (or quite understand what they mean).
There are many examples in C#, but I haven't been able to find one that demonstrates it in PoSH.
Example Scenario:
Assume $DeletedUsers contains a list of User objects of PSCustomObject type. With typical "User" properties such as department or Employment status. This list should be be capable of being passed to functions that will change statuses of the users property that can then be added to a separate output list of the same Generic.List type.
Currently any changes by the example function.
Function ProcessUser {
[Cmdletbinding()]
Param($DeletedUsers)
begin{$DeletedUsersClone = $($DeletedUsers).psobject.copy()} #OR similar
process{
$DeletedUsersClone | foreach { $_ | Add-Member -NotePropertyName
"Processed" -NotePropertyValue "Processed:00"; $Outputlist.add($_)}
}
}
Impacts the original $DeletedUsers, erroneously adding processed information to a list that should stay static.
There are alternate ways to prevent this from impacting the ultimate objective of the script, but the question is:
How do I create a True, non-referenced clone of a System.Collections.Generic.List[System.object] using built-in C# methods.
The trick is to use a scriptblock with an explicit cast to the delegate type. This looks like:
$DeletedUsers.ConvertAll([converter[object,object]] {param ($i) <# do convert #> })
Note:
As became clear later, the OP is looking for a deep clone of the original list; i.e., not only should the list as a whole be cloned, but also its elements.
This answer only shows how to create a shallow clone (and how to pass a list read-only).
See Bruce Payette's helpful answer for a deep-cloning approach based on the .ConvertAll method; with [pscustomobject] instances, you'd use the following (but note that .psobject.Copy() only creates shallow copies of the [pscustomobject] instances themselves):
$DeletedUsers.ConvertAll([converter[pscustomobject, pscustomobject]] {param ($obj) $obj.psobject.copy() })
If you want to pass a shallow clone of your list to a callee:
Pass [Collections.Generic.List[object]]::new($DeletedUsers) (PSv5+ syntax)
Alternatively, if the type of the list elements isn't known or if you don't want to repeat it, pass: $DeletedUsers.GetRange(0, $DeletedUsers.Count)
If you just want to prevent accidental modification of your list by a callee:
Pass $DeletedUsers.AsReadOnly() - however, that does change the type, namely to [Collections.ObjectModel.ReadOnlyCollection[object]]
When I run Get-ChildItem in a directory with only one file, I get a single DirectoryInfo object:
PS H:\> (ls).GetType().Name
DirectoryInfo
As soon as I add a second file, the output becomes an array:
PS H:\> (ls).GetType().Name
Object[]
How should I deal with this dichotomy in a function? Ideally, I'd like to force it to return an Array even when there's only one element, preferably without having to put in conditional logic based on the result of GetType() or Length or whatever.
Use array operator #(): $Array=#(ls). That operator guaranteed that you will have an array even if pipeline return zero or one object.
Expanding on PetSerAl's answer, you could also cast the type you need more explicitly:
[array](ls) will get you a System.Array object with a single member, so you could use this in a place where you want to avoid creating a new variable but need a specific type
You can also specify arrays that contain only specific types by casting: [int[]]$integersOnly = 1,2,3 will give you a System.Array object that can only hold objects of type [int]
Keep in mind you can use .Net classes - what if you want an array you can modify easily? [System.Collections.ArrayList](ls) does that, enjoy using the Remove() method
A few other hints, while I'm at it:
Want to see what you can do with an object of a specific type? Pipe it to Get-Member, the single most useful command I can think of; it'll show you everything you can do with the object
Curious about a class and what it can do, or looking for details like the different constructors that are available? Just enter [<class_name_here>] and if the assembly is loaded it'll show you everything you want to know
Working on a simple helper function in PowerShell that takes a couple of parameters and creates a custom Enumerable object and outputs that object to the pipeline. The problem I am having is that PowerShell is always outputting a System.Array that contains the objects that are enumerated by my custom Enumerable object. How can I keep PowerShell from unpacking the Enumerable object?
The code: http://gist.github.com/387768
Try to change the line 46 from
$row
to
, $row
EDIT: as Johannes correctly pointed out, the unary operator comma creates an array with one member.