Replace not working when Looping through an Array - arrays

Working on a project and I have a need to replace a 'path prefix' with a different prefix. Automating the copying of files and then I will do using those files locally. Rather than making new variables I figured I would re-purpose old ones I would not need after the copy is complete. Ran into an issue with doing a replace when looping through the Array. I was able to find a solution for this particular scenario but would like to understand why my loops were not working
No errors are shown to give me an indication why it is not working.
Any help on understanding why the replace isn't working when looping would be helpful
Sample code of how I am building paths
[string]$Root_Path = "\\Main_Blah_Path\"
[string]$Software = $Root_Path + "Software\"
[string]$Adobe_Reader = $Software + "Adobe_Reader\"
[string]$Firefox = $Software + "Firefox\"
[string]$Google_Chrome = $Software + "Google_Chrome\"
[System.Collections.ArrayList]$List_Of_Software = #(
$Adobe_Reader
$Firefox
$Google_Chrome
)
Example of the ways I have done the replacement. These work and will write the desired output to the console
foreach ($Path in $List_Of_Software) {
$Path -replace '\\\\Main_Blah_Path\\','C:\Folder\'
}
$List_Of_Software | ForEach-Object {$_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}
Example of failures I am having. I cannot replace and save the data into itself to do a replacement. I couldn't get .replace to work at all
foreach ($Path in $List_Of_Software) {
$Path = $Path -replace '\\\\Main_Blah_Path\\','C:\Folder\'
}
$List_Of_Software | ForEach-Object {$_ = $_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}
foreach ($Path in $List_Of_Software) {
$Path.Replace('\\\\Main_Blah_Path\\','C:\Folder\')
}
Solution I am using for my current scenario but I can foresee a few things in my future where this method may not be a viable option
$List_Of_Software = $List_Of_Software -replace '\\\\Main_Blah_Path\\','C:\Folder\'

You're almost there, you just need to assign the results of the replacement back to the variable when you're looping the array. Try:
$List_of_software = $List_Of_Software |
ForEach-Object {$_ -replace '\\\\Main_Blah_Path\\','C:\Folder\'}

Related

Check a object-string against a array of exceptions

I got a bunch of objects containing multiple string values. I want to manage the exceptions with a object/array consisting of multiple more and less known exceptions. Is there a simple way to get this to work? afraid my brain ain't made for this kind of problemsolving.
Example:
Exceptions =#{
Known1 = 'Minor Error'
Known2 = 'This Have We Seen Before'
Known3 = 'I Remember This Issue'
Special1 = 'Application malfunction'
Special2 = 'Application malfunction severe!'
}
$AllCases = Get-Cases
Foreach ($Case in $AllCases){
if ($Case.Name -match $Exceptions.Values.('^Known+[0-9]$')){'Do That again'}
Elseif ($Case.Name -match $Exceptions.Values.Special1){'OBS! Do This Fast'}
Elseif ($Case.Name -match $Exceptions.Values.Special2){'SEVERE OBS! do this faster!'}
}
Thanks :)
I guess you're trying to create a script to identify incidents where $case.Name whatever that could be (subject of the incident?) matches with your map of words that are commonly found, which in turn points to a specific Error Code or Error Index? If that's the case, It is probably better even though slower, to store a separate CSV file where you can add new Error Indexes and make your script read this CSV each time. In any case, I harcoded an example of how the CSV could look like and added some comments to guide you with the tought process.
$Exceptions = #"
Index,Error
Known1,Minor Error
Known2,This Have We Seen Before
Known3,I Remember This Issue
Special1,Application malfunction
Special2,Application malfunction severe!
"# | ConvertFrom-Csv
# I'll generate 500 random cases here mixing the words on Error
# I'll also added a IncidentNumber property becase I'm guessing you have something like that
$words = $Exceptions.Error -split '\s'
$AllCases = 0..500 | ForEach-Object {
$ran = Get-Random -Minimum 2 -Maximum 10
[pscustomobject]#{
IncidentNumber = Get-Random
Name = ($words | Get-Random -Count $ran) -join ' '
}
}
Example of the cases created
PS /> $AllCases | Get-Random -Count 5
IncidentNumber Name
-------------- ----
2043166219 Application This Remember Before
837011116 malfunction Error Seen Have malfunction Issue Minor severe!
2103904733 This malfunction We This I Have Seen Application
323914959 Minor This Issue Remember This Application Have Seen
1105202359 malfunction Seen We This I malfunction
Back to the script:
$result = foreach($Case in $AllCases)
{
if($Exceptions.Error -match $Case.Name)
{
# Note: I'm using -match here instead of -in or -contains
# because we want to partially match, considering that $Case.Name
# could be a much longer string
# $Exceptions array where the string in $Case.Name
# matches any element of $Exceptions.Error
$Exceptions.Where({$_.Error -match $Case.Name}) |
Select-Object #{n='IncidentNumber';e={$Case.IncidentNumber}},Index,#{n='Status';e={'FOUND'}}
continue
}
# If there are no matches you can use something like below, not sure
# you have some sort of incident number or something short you can use
# for those incidents not found :)
$Case | Select-Object IncidentNumber,#{n='Index';e={$null}},#{n='Status';e={'NOT FOUND'}}
}
Since this is all random, it is possible there will not be any matches but in this run I got these results
PS /> $result | Sort-Object Status | Select-Object -First 10
IncidentNumber Index Status
-------------- ----- ------
505966803 Special1 FOUND
235217253 Known3 FOUND
1034830172 Known3 FOUND
1047600080 Special2 FOUND
481579809 Known3 FOUND
2131683661 Known2 FOUND
505966803 Special2 FOUND
1424263573 NOT FOUND
249127953 NOT FOUND
489126244 NOT FOUND

Count occurrences of something in an array inside a foreach loop

I have a product CSV file that I have imported into $products
If something occurs more than once with the same name I want to populate the ParentSKU field, otherwise leave it blank.
Excuse the pseudocode but I'm imagining something like this:
foreach ($item in $products) {
if ($item.name.count -gt 1) {
$item.ParentSKU = $item.name }
else { } # do nothing
}
$item.name.count isn't correct but I hope my thinking is on the right track?
Many thanks for any advice
Powershell Object lists aren't smart enough to know that there's multiple of any one item, so you're going to have to iterate through (manually or otherwise) to find whether there's multiples here.
Since you're going to be making modifications to any duplicates, it may make sense to loop through and find duplicates manually, but it doesn't really follow the "powershell" philosophy / approach.
If you want to use powershell's built-in & powerful piping features, you might try a solution like this, which would grab all the PSObjects with duplicates using Where-Object, then sets the values for all those PSObjects.
$products |
Group-Object -Property Name |
Where-Object -FilterScript {
$_.Count -gt 1
} |
Select-Object -ExpandProperty Group |
Foreach-Object { $_.ParentSKU = $_.Name }
Since everything is passed by reference, your $products object will have the modified values!

Attempting to use two arrays as variables to search file names (powershell)

I need to search multiple (named) folders on multiple servers for files matching a specific date and copy those files to a local folder using Powershell. The number of folders are not the same size arrays as the number of servers. I.e. I need \server1\interfacefolders\folder1, \server1\interfacefolders\folder2, \server2\interfacefolders\folder1, \server2\interfacefolders\folder2, etc.
I have the following set up as arrays/variables preparing for this, I thought "nested" foreach loops would work, but it bombs out...any ideas how to get started on this?:
[string[]]$ProdServerArray = "server1", "server2", "server3"
[string[]]$InterfaceArray = "folder1", "folder2" "folder3" do {
$date = Read-host "Enter date (MM/DD/YYYY) : " } while ($date -as [datetime] -isnot [datetime])
$date = $date -as [datetime]
$destination = new-item c:\GetFilesResults\$($date.toshortdatestring().replace("/","-")) -type directory
$path = foreach ($ProdServer in $ProdServerArray)
{
$folder = foreach ($Interface in $InterfaceArray)
{
$file = "\\$path\InterfaceFolder\$folder\*"
if ("$file".LastWriteTime -gt $date.date)
{
Copy-Item -Path $file.fullname -Destination $destination
}
}
}
to build the full folder names from those two arrays, you can use two nested foreach loops. once you have the values, you can build the paths via something like the -f string format operator.
i left out the rest of your code since it does not appear to pertain to the question you asked. [grin]
$ProdServerArray = 'serverAAA', 'serverBbBbBb', 'server_CCC'
$InterfaceArray = 'folder1', 'folder2', 'folder3', 'folder666'
foreach ($PSA_Item in $ProdServerArray)
{
foreach ($IA_Item in $InterfaceArray)
{
'\\{0}\InterfaceFolders\{1}' -f $PSA_Item, $IA_Item
}
'=' * 30
}
output ...
\\serverAAA\InterfaceFolders\folder1
\\serverAAA\InterfaceFolders\folder2
\\serverAAA\InterfaceFolders\folder3
\\serverAAA\InterfaceFolders\folder666
==============================
\\serverBbBbBb\InterfaceFolders\folder1
\\serverBbBbBb\InterfaceFolders\folder2
\\serverBbBbBb\InterfaceFolders\folder3
\\serverBbBbBb\InterfaceFolders\folder666
==============================
\\server_CCC\InterfaceFolders\folder1
\\server_CCC\InterfaceFolders\folder2
\\server_CCC\InterfaceFolders\folder3
\\server_CCC\InterfaceFolders\folder666
==============================
First of all, you are missing a comma in the line:
[string[]]$InterfaceArray = "folder1", "folder2" "folder3"
Additionally, as far as I can tell, your do while loop doesn't appear to be accomplishing anything, as the only time this will ever be true is if the time 12:00:00AM exactly on the date specified. No matter what date you input in the format (MM/DD/YYYY), they will not be equal unless the case I said above.
Since you are searching multiple servers, Invoke-Command is your friend, as a foreach loop will act in series, while this will work in parallel. It will send out the search command to each server simultaneously.
I am not quite sure exactly what you are trying to do, so I did not fill in the actual search code(seen below), but the part i have left blank would be where you enter what filename/filename schema you are looking for. If you provide more clarity I can assist further if needed.
(Note: $filepath, although self explanatory, is the file paths you wish to search. You can generate them in a way similar to the one provided by Lee_Dailey. I'd recommend removing the divider lines and saving the paths generated to a String System.Array Object)
Invoke-Command -ComputerName $ProdServerArray -ScriptBlock {Get-Childitem –Path $filepath -Recurse -ErrorAction SilentlyContinue |where {<your code here>}}

Test-Connection $False will not convert to ArrayList

Currently working on making a new report that will be generated with PowerShell. Using PowerShell to build a HTML email. I have one other report working fine but ran into an unexpected issue on this one.
The below code is just s sample from the script I am still building. Still adding pieces to the script but testing it as I move forward. I added a Test-Connection to see if a computer was responding or not and lost the ability to build an array.
My final goal with this report is to import a list of names from a file and then loop over all of the computers to see if they are pinging and gather some information from them using Get-WMIObject, etc.
The below code will replicate the issue I am having but I am not sure how to solve it. I've narrowed down the issue to when Test-Connection returns 'False'. On line 26 I am filtering for just results that returned a 'False' on Test-Connection to save them into its own array so that I can use that array in a different part of my code to build the HTML table/HTML to send out the email.
Only the flipside, if I tell it to look for only 'True', it will save into the array without issue.
This is the error that PowerShell is giving when doing filtering by 'False'.
Cannot convert value "#{Computer_Name=Computer1; Ping_Status=False}" to type "System.Collections.ArrayList". Error: "Cannot convert the "#{Computer_Name=Computer1 Ping_Status=False}" value of type "Selected.System.Management.Automation.PSCustomObject" to type "System.Collections.ArrayList"."
Please let me know if there is any other information that I can provide. I've been stuck on this one for a while. Co-workers are even say this is a weird one.
Is there something unique about the way Test-Connection return a 'False'?
CLS
[string]$ErrorActionPreference = "Continue"
[System.Collections.ArrayList]$Names = #(
"Computer1"
"Computer2"
)
[System.Collections.ArrayList]$WMI_Array = #()
[System.Collections.ArrayList]$Ping_Status_False = #()
foreach ($Name in $Names) {
[bool]$Ping_Status = Test-Connection $Name -Count 1 -Quiet
$WMI_Array_Object = [PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = $Ping_Status
}
$WMI_Array.Add($WMI_Array_Object) | Out-Null
}
$WMI_Array | Format-Table
[System.Collections.ArrayList]$Ping_Status_False = $WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
$Ping_Status_False
The problem is not Test-Connection but that this statement
$WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
produces just a single result. Which is not an array, and can thus not be converted to an ArrayList. The behavior is identical when you filter for $_.PingStatus -eq $true with just a single matching object, so I suspect that you had either more than one successfully pinged host or none at all when you tested that condition and it didn't throw the same error.
You could mitigate the problem by wrapping the statement in the array subexpression operator:
[Collections.ArrayList]$Ping_Status_False = #($WMI_Array |
Where-Object {$_.Ping_Status -eq $false} |
Select-Object Computer_Name, Ping_Status)
Or, you could simply drop all the pointless type-casting from your code:
$ErrorActionPreference = "Continue"
$Names = 'Computer1', 'Computer2'
$WMI_Array = foreach ($Name in $Names) {
[PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = [bool](Test-Connection $Name -Count 1 -Quiet)
}
}
$WMI_Array | Where-Object { -not $_.Ping_Status }

Extract partial line of text from text file

I need to keep the first 5 characters from data being pulled from a text file.
Data looks like this:
S1831KWT0081
S2004KWT0083
S2351KWT0085
S0054KWT0087
Results should looks like this:
S1831
S2004
S2351
S0054
I can get it working when setting the variable within PowerShell:
PS> $a = "S1831KWT0081"
PS> $a.Substring(0, $a.IndexOf('K'))
S1831
but I'm stuck when trying to pull from a text file.
To solve this, you will need to parse the text file on a line-by-line basis. Basically treating each line as a value in an array.
Get-Content location.txt | foreach { $_.Substring(0, $_.IndexOf('K')) }
Another option would be a regular expression replacement:
(Get-Content 'C:\path\to\input.txt') -replace '^(.{5}).*', '$1'
That would also allow you more specific matches, e.g. like this:
$re = '^([a-z]\d{4}).*'
(Get-Content 'C:\path\to\input.txt') -match $re -replace $re, '$1'
Just to show there always is more than one PoSh way ;-)
gc .\input.txt|%{$_.split('K')[0]}
Or the more verbose version
Get-Content .\input.txt |
ForEach-Object { $_.split('K')[0] }

Resources