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

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>}}

Related

How do I access properties of nested arrays in powershell within foreach loops?

I'm trying to write a script that checks whether a group of registry keys are set individually and then either sets them or modifies them conditional on thier current state.
The keys that I want to check/set contain a mix of Strings and DWORDs
I want to loop through an array that contains the key I want to set paired with the value.
I've tried as a hashtable/splatting but the input to Get-ItemProperty fails because of the value parameter so I tried basic arrays instead with no luck.
They are all at the same registry path but I was attempting to do something similar to this:
$Path = "HKLM:\Software\path\to\keys"
$Properties = (
('key', value),
('key2', value2),
('key3', 'value3')
)
foreach ($item in $Properties){
$exist = Get-ItemProperty -Path $Path -name $item[0]
if ($exist) {
Set-ItemProperty -Path $Path -Name $item[0] -Value $item[1]
} else {
New-ItemProperty -Path $Path -Name $item[0] -Value $item[1]
}
}
But no matter what I've tried I cannot retrieve the individual elements of the inner arrays.
I realize I could probably do this long-form and just do it line by line rather than attempting to iterate through, but this is definitely a more elegant way, and would be a great template if I need to do something similar in the future.
Holy moly. Nothing breaks you out of a rut like posting the question to an online forum.
The .GetValue() method is what I needed, not the raw index number.
...So $Item.GetValue(0) for the key name and $Item.GetValue(1) for the value.
Feel silly answering my own question but hopefully it helps someone else!

Possible to have different array names when write-output PSCustomObject]#{ in my function?

Is there any way to name the variable for an array something in my Catch { and then have another array name for the array inside Try/script part of my function?
Cause when i try doing like this $computerObject = [PSCustomObject]#{
and then doing Write-Output $computerArray i can only get either my variables inside Try/script array being displayed inside Powershell window. Or only get the $error message from my Catch.. Is there any way to name each array something so i can do like below.
write-host "Results"
Write-Output $computerArray - display my first array here
write-host "Failed: computerlist" -foregroundcolor red
Write-Output $computerArray2 - display $error computers here. $error should just include computers who did not answer to ping and other stuff from my invoke-command computerlist.txt
The only true answer to why i need this separately is that sometimes i want my array in a CSV file. And sometimes i just want to copy info directly from Powershell window. And then its more practical to have failed computers separated and not in the same array output.
This function (as mentioned in comments) doesn't leverage the CIM cmdlets parallel capabilities, would recommend some tweaks to it but to answer the actual question, how can you "split" the output between success and fail:
The function as-is, doesn't require any modification to achieve this, it's try and catch blocks are outputting objects with the same properties and luckily one of those properties is Error and it's value is a boolean so you can simply first query all the computers and then split the result using .Where with Split mode.
The code would be like this:
$computers = 'computer1', 'computer2', ....
$computerArray = foreach($computer in $computers) {
Get-ComputerInformation -ComputerName $computer
}
# now we can split between FAIL and SUCCESS
$fail, $success = $computerArray.Where({ $_.Error }, 'Split')
$success | Export-Csv path\to\success.csv -NoTypeInformation
$fail | Export-Csv path\to\fail.csv -NoTypeInformation

Recursively delete folders where folder name contains a number

Good afternoon all,
I am attempting to delete folders at a specific location containing a number in the name, which can be any number in the array.
$fso = New-Object -com "Scripting.FileSystemObject"
$Versionarray = (13..20)
$folder =
$fso.GetFolder("$env:USERPROFILE\appdata\local\Microsoft\OneDrive")
foreach ($subfolder in $folder.SubFolders)
{
If ($subfolder.Name -match "$Versionarray")
{
remove-item $subfolder.Path -Verbose
}
}
Please see an example of the following folders it will sift through below:
18.172.0826.0010
18.172.0826.0010_2
18.172.0826.0015
18.172.0920.0015
18.172.0920.0015_1
logs
settings
setup
If I change the "VersionArray"array to the variable "18" instead, it will start to remove the folders. It doesn't appear to be going through each number of the array. I need it to be an array to future-proof the script as the number represents a version of OneDrive.
Thank you for looking over this.
Going from your initial idea to have a list of items that must be contained in the subfolder name, we can do a pipeline like this:
$Versionarray = 13..20
Get-ChildItem "$env:LOCALAPPDATA\Microsoft\OneDrive" -Recurse | Where-Object {
$item = $_
$item -is [System.IO.DirectoryInfo] -and (
$Versionarray | Where-Object { $item.Name.Contains($_) }
)
} | Remove-Item -WhatIf
Notes:
Get-ChildItem returns all subfolders and files in a folder. Drop -Recurse if you don't need that.
Where-Object filters any list of objects according to a condition. Any result other than 0, $false, $null, or the empty string/empty list will be considered $true. It's not necessary to actually return $true, as long as anything is returned at all.
$_ is the "current item" in the pipeline
$foo -is [Fully.Qualified.ClassName] checks if an object is of a certain class. In this case, we only want to look at System.IO.DirectoryInfo objects and ignore all files.
$Versionarray | Where-Object { $item.Name.Contains($_) } filters the $Versionarray down to those elements that are contained in the folder name. You could use .StartsWith() or any other method of .NET strings in its place.
Any object that "survives" the Where-Object filter is passed to Remove-Item
-WhatIf performs a dry-run, drop it when you're sure the right thing will happen.

ACL "fuzzy" comparision

I'm trying to compare ACLs on a folder with a reference set of ACLs, and then list any exceptions. The "fuzzy" part of the equation is that I want to be able to disregard any unknown SID. So creating a reference folder with the perms I want to test won't work to use Compare-Object between it and my test folder.
The underlying scenario is that I am cleaning up old user directories where the actual user account has been deleted (this is where the non-resolved SID comes in). By default, the folders include perms for Administrator and the like, which I don't care about. There are some folders, however, where another user has been granted explicit permissions, and I want to capture these. Unfortunately, there aren't any shortcuts I can use to check: e.g. -IsInherited or the like to exclude ACLs I don't care about.
Per the below, I can dump the ACLs out into an array
$acl = get-acl f:\user_folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
$access
BUILTIN\Administrators
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390
MYDOMAIN\Domain_Group ###Yes, the group has an underscore in the name
I can create another array of the users I want to ignore, including a partial string to match any unresolved SID.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup","S-1-5-21")
So how do I compare my $defaults array with the $access array and output only the exceptions like "MYDOMAIN\JBLOGGS"?
I'm trying a foreach, but I'm stumped about grabbing that exception. The following still outputs the SID I want to avoid. I'm hoping to also avoid too many nested "IFs".
$access | ForEach { If ($defaults -notcontains $_) { Write-Output $_ } }
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390 #Do not want!
If I put the wildcard $_* into the -notcontains, I get the whole contents of $access again.
I'd do something like this:
$defaults = 'BUILTIN\Administrators', 'MYDOMAIN\DomainGroup', 'S-1-5-21*'
$acl.Access | Where-Object {
$id = $_.IdentityReference
-not ($defaults | Where-Object { $_ -like $id })
} | Select-Object -Expand value
$defaults | Where-Object { $_ -like $id } does a wildcard match of the given identity against all items of $defaults. The wildcard * at the end of S-1-5-21* allows to match all strings starting with S-1-5-21. The negation -not inverts the result so that only identities not having a match in $defaults pass the filter.
give the users you want to ignore some right on a dummy folder, get the acl of that folder and then compare whith the acl of your actual folder
$genericACL = get-acl c:\temp\dummy
$folderacl = get-acl f:\user_folder
$exceptions= $folderacl.Access.identityreference.value |?{ ($_ -notin $genericACL.access.identityreference.value) -and ($_.strartswith('S-1-5-21') -eq $false)) }
In the end, it was fairly simple, thanks to the help above.
I managed to omit the fact in the original question where I required it to work in Powershell v2.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup")
$acl = get-acl $folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
# check that no other account still has access to the folder
$access | ForEach {
If ($defaultACL -notcontains $_ -and $_ -notlike 'S-1-5-21*') {
write-output "Extra perms:$user $_"
}

Powershell V2.0 SHA1 Calculation

I posted a question on Monday about how I can calculate SHA1 hash within powershell.
The resultant code was as follows:
$file = 'C:\Zip\File.zip'
$sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
[System.BitConverter]::ToString( $sha1.ComputeHash([System.IO.File]::ReadAllBytes($file)))
This code works perfectly and does what I need it to do, but at the moment I have to specify the file I want the SHA1 hash to be calculated for. Is there any way I can get it to calculate the hashes for each files within that 'zip' folder.
I've been attempting to do this for the last two days by using a loop etc, and I just haven't moved anywhere. It doesn't help that my PowerShell skills are appalling.
Any help is greatly appreciated.
So basically, you just want to
Get a list of all files within C:\Zip\ and
Apply that snippet of code to each of them.
You want to look into using ForEach loops (can also be expressed in shortened-form, for use with piping, like so: $var | % {<#do this for each #>}).
You can read more about ForEach loops on all over the internet - two sites I have great luck with for researching PowerShell topics are TechNet and the Hey, Scripting Guy! Blog. I'd suggest checking them out, and reading up.
This should be a good start:
$zips = Get-ChildItem 'C:\Zip\' -Filter *.zip
$sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
foreach ($file in $zips) {
$return = "" | Select Name, Hash
$return.name = $file.Name
$return.hash = [System.BitConverter]::ToString($sha1.ComputeHash([System.IO.File]::ReadAllBytes($file.FullName)))
Write-Output $return
}

Resources