2

Good morning all!

I've been messing around with the switch statement since I found out about it on another post I made.

I have this issue with the code below, where its printing multiple rows with the same information and, I get why its doing so but, I dont know how to fix it. I believe its messing up when im assigning the variable but, im not too sure. Can someone point me in the right direction on what may be causing the issue? Any help is appreciated.

$gc = Get-ChildItem -Path 'C:\users\abrah\OneDrive\Desktop'  
Foreach ($File in $gc) {
 
    switch -Wildcard ($file) {

        "deskt*" { $Desk = "This is the location: $($File.FullName)" }
        "*v*" { $VA = "This is the location: $($File.FullName)" }

    }

    $VCount = $va | Measure-Object | Select-Object -ExpandProperty Count
    $Dcount = $Desk | Measure-Object | Select-Object -ExpandProperty Count

    $PS = [pscustomobject]@{
        DesktopLocation = $Desk
        DCount          = $Dcount
        VLocation       = $VA
        VCount          = $VCount
 
    }

    $PS
}

About the script: Im just looking to find any files on my desktop that begin with deskt, and any with the letter V in it. Then im throwing it into a custom object while attempting to count how many files contain those key letters.

Here are the results btw:

enter image description here

Mathias R. Jessen
  • 135,435
  • 9
  • 130
  • 184
Abraham Zinala
  • 2,812
  • 2
  • 6
  • 22

3 Answers3

5

As for your switch-statement-based approach:

  • switch itself is capable of processing collections, so there's no need to wrap it in a foreach loop.

  • What you're looking for is to build up two collections from the input, which requires you to:

    • initialize $Desk and $VA as a collection data type.
    • append to these collections in the switch branch handlers.
# Initialize the collections.
$Desk = [System.Collections.Generic.List[string]] @()
$VA = [System.Collections.Generic.List[string]] @()

# Make `switch` loop over the .FullName values of all items in the target dir.
switch -Wildcard ((Get-ChildItem C:\users\abrah\OneDrive\Desktop).FullName) {
  '*\deskt*' { $Desk.Add("This is the location: $_") } # add to collection
  '*\*v*'    { $VA.Add("This is the location: $_") }   # add to collection
}

# Construct and output the summary object
[pscustomobject] @{
  DesktopLocation = $Desk
  DCount          = $Desk.Count
  VLocation       = $VA
  VCount          = $VA.Count
}

Note:

  • While it is possible to use an array as the collection type, "appending" to arrays with +=, while convenient, is inefficient, because a new array must be created behind the scenes every time, given that arrays are immutable with respect to their element count.
    While it may not matter for arrays with only a few elements, it's a good habit to form to use System.Collections.Generic.List`1as an efficiently extensible collection type.

  • That said, given that statements such as switch and foreach loops can act as expressions when assigned to a variable, if all output is to be captured in a single collection, you don't even need a collection type explicitly, which is both concise and efficient; e.g.:
    $collection = foreach ($i in 0..2) { $i + 1 } stores array 1, 2, 3 in $collection; note that if only one object is output, $collection will not be an array, so to ensure that you can use [array] $collection = ...


Alternatively, a simpler solution is to take advantage of the fact that wildcard-based filtering via the -Filter parameter is fast, so that even calling Get-ChildItem twice likely won't be a problem:

$dir = 'C:\users\abrah\OneDrive\Desktop' 
[array] $Desk = (Get-ChildItem -LiteralPath $dir -Filter deskt*).FullName
[array] $VA = (Get-ChildItem -LiteralPath $dir -Filter *v*).FullName

[pscustomobject] @{
  DesktopLocation = $Desk
  DCount          = $Desk.Count
  VLocation       = $VA
  VCount          = $VA.Count
}
mklement0
  • 312,089
  • 56
  • 508
  • 622
  • `Exception calling "Add" with "1" argument(s): "Collection was of a fixed size."` This is the error i get when attempting to run the first block of code, the second one works fine but, im not sure why the first doest. – Abraham Zinala Jan 10 '21 at 22:45
  • Honestly, I was just looking to work with `switch` to see all it could do. Im aware i could've done it like 6 different ways to do it but, was trying to experiment a bit. Really was trying going to apply this new method against an AD Query, for users with certain words in their name. – Abraham Zinala Jan 10 '21 at 22:51
  • 1
    Understood, @AbrahamZinala. As for the error message: that implies that you used an _array_ rather than a `[System.Collections.Generic.List[string]]` instance, as shown in the first snippet. Arrays are fixed-size collections, but because they also implement the [`System.Collections.IList`](https://docs.microsoft.com/en-US/dotnet/api/System.Collections.IList) interface, they do have an `.Add()` method - which _always_ fails. – mklement0 Jan 10 '21 at 22:56
  • Is there anything you recommend me looking into to learn these types of things? Books, videos, forums? You seem to understand this pretty well – Abraham Zinala Jan 10 '21 at 23:00
  • @AbrahamZinala: A while back I compiled [this](https://stackoverflow.com/a/48491292/45375). – mklement0 Jan 10 '21 at 23:05
  • When reading this answer, one might wonder why it is efficient to use arrays in the 2nd example? Does PowerShell use an array-resizing algorithm similar to `System.Collections.Generic.List`, which manages a capacity (the actual size of the internal array member) in addition to the length and increases the capacity in multiples of two? – zett42 Jan 11 '21 at 10:23
  • 1
    @zett42: I've never really looked into the implementation, but I _think_ that an [`ArrayList`](https://docs.microsoft.com/en-US/dotnet/api/System.Collections.ArrayList), which grows efficiently, is used behind the scenes and then converted to an array. Somewhat related [GitHub discussion](https://github.com/PowerShell/PowerShell/issues/5643#issuecomment-349811857). If you want to experiment with relative performance: `$a = 1..1e6; Time-Command { $l = [System.Collections.Generic.List[object]] @(); foreach ($e in $a) { $l.Add($e) } }, { $l = foreach ($e in $a) { $e } }` – mklement0 Jan 11 '21 at 12:31
3

You don't need a switch or a foreach loop to count how many files matching each pattern you have:

# Fetch files
$gc = Get-ChildItem -Path 'C:\users\abrah\OneDrive\Desktop'  

# Count those starting with "Deskt"
$desktopFiles = $gc |Where-Object Name -like "Deskt*"

# Count those matching `*v*`
$vFiles = $gc |Where-Object Name -like "*v*"

# Output details
[PSCustomObject]@{
  DesktopLocations = @($desktopFiles.FullName)
  DesktopCount     = $desktopFiles.Count
  VLocations       = @($vFiles.FullName)
  VCount           = $vFiles.Count
}
Mathias R. Jessen
  • 135,435
  • 9
  • 130
  • 184
2

Other solution:

$Grouped=Get-ChildItem -Path 'C:\users\abrah\OneDrive\Desktop'  -file | %{

if ($_.Name -like "Deskt*")
{
    Add-Member -InputObject $_ -MemberType NoteProperty -Name "TypeFile" -Value "DESK_FILE"
}
elseif ($_.Name -like "*v*")
{
   Add-Member -InputObject $_ -MemberType NoteProperty -Name "TypeFile" -Value "V_FILE"
}
else
{
   Add-Member -InputObject $_ -MemberType NoteProperty -Name "TypeFile" -Value "OTHER_FILE"
}

$_

} | Group TypeFile -AsHashTable

#result
$Grouped

#if you want files for a group 
$Grouped["V_FILE"] 

#if you want really your result
[PSCustomObject]@{
  DesktopLocations = $Grouped["DESK_FILE"].fullName 
  DesktopCount     = $Grouped["DESK_FILE"].Count
  VLocations       = $Grouped["V_FILE"].fullName 
  VCount           = $Grouped["V_FILE"].Count
}
Esperento57
  • 15,129
  • 3
  • 36
  • 41