Powershell: Dynamic Menu-based program to copy file - powershell

The purpose of this was to copy one (or more) files from one user selected dynamic location to another user selected dynamic location.
Write-Host "Choose where to copy FROM"
$1MainFolder = (Get-ChildItem "C:\Users\$env:USERNAME\FROM" -Directory | Sort-Object)
$menu = #{}
for ($i=1;$i -le $1MainFolder.count; $i++) {
Write-Host "$i. $($1MainFolder[$i-1].name)"
$menu.Add($i,($1MainFolder[$i-1].name))
}
[int]$ans = Read-Host 'Enter selection'
$selection = $menu.Item($ans)
# The directory after the selection is only one, however it's different for every user; this remains the same for "COPY TO"
$First = (Get-ChildItem "C:\Users\$env:USERNAME\FROM\$selection" -Directory)
# This directory is the same for every user; this remains the same for "COPY TO"
$DataFROM = (Set-Location $First\USERFILE)
Clear-Host
Write-Host "Choose where to copy TO"
$2MainFolder = (Get-ChildItem "C:\Users\$env:USERNAME\TO" -Directory | Sort-Object)
$menu2 = #{}
for ($i=1;$i -le $2MainFolder.count; $i++) {
Write-Host "$i. $($2MainFolder[$i-1].name)"
$menu2.Add($i,($2MainFolder[$i-1].name))
}
[int]$ans2 = Read-Host 'Enter selection'
$selection2 = $menu2.Item($ans2)
$Second = (Get-ChildItem "C:\Users\$env:USERNAME\TO\$selection2" -Directory)
$DataTO = (Set-Location $Second\USERFILE)
Clear-Host
That completes section one and two. The third section allows the user to then pick the file they want copied - this is a separate function which works well, however it errors in conjunction with this. I've since simplified the function to obtain the same error with:
# The Select-Object section being the simplified part...
Get-ChildItem $DataFROM | Select-Object "DATA.log*" | Copy-Item -Destination $DataTO
What I expect to happen is for the data to be copied from the initial dynamic location to the final dynamic destination.
What happens is that I get an error stating that it "Cannot bind argument to parameter 'Path' because it is an empty string." to which it points at the $DataTO variable.
This then lead me to disregard the third section and test the variables to which I found that if I use the following after the first two sections:
Get-ChildItem $DataFROM
# or
Get-ChildItem $DataTO
it returns back information from the last selected menu, disregarding the variables entirely. So then are the dynamic menus not to be used in this manner or perhaps I am using the menus wrong?
Would someone please advise where I've gone astray?

Update: I've since re-worked it to the following that fits my requested needs: (hopefully this will aid someone else needing something similar)
[array]$sMainFolder = Get-ChildItem "C:\Users\$env:USERNAME\DATA-TO_FROM" -Directory | ForEach-Object {
Get-ChildItem $_.FullName | ForEach-Object {
$found = Get-ChildItem $_.FullName | Where-Object { $_.Name -eq 'USERFILE' } | Select-Object -First 1
if ($found) {
[PSCustomObject]#{
RootFolder = [string]$_.FullName
Path = $found.FullName
}
}
}
}
[array]$dMainFolder = Get-ChildItem "C:\Users\$env:USERNAME\DATA-TO_FROM" -Directory | ForEach-Object {
$subs = Get-ChildItem $_.FullName
$found = $subs | ForEach-Object { Get-ChildItem $_.FullName } | Where-Object { $_.Name -eq 'USERFILE' } | Select-Object -First 1
if ($found) {
[PSCustomObject]#{
RootFolder = [string]$_.FullName
Path = $found.FullName
}
}
}
function ShowMenu ($Folders, $Message) {
Write-Host $Message
for ($i = 1; $i -le $Folders.count; $i++) {
$name = $Folders[$i - 1].RootFolder
Write-Host "$i. $name"
}
[int]$ans = Read-Host 'Enter Selection'
Return $Folders[$ans -1].Path
}
$DataFROM = ShowMenu $sMainFolder "Choose where to copy FROM"
$DataTO = ShowMenu $dMainFolder "Choose where to copy TO"
Clear-Host
Copy-Item "$DataFROM\*" $DataTO -Recurse -Force

Related

Create a Clickable Folder Link in Powershell

I have a script that searches for a file name in a folder and gives a folder path. Does Powershell have a way to make these folder paths a clickable link? I want to bring up a list of file directory paths that I can click on.
Here is what the code looks like.
#Declare Variables.
$Software = #()
#Delcares Directories to search.
$Directories = #(
'\\Software\Unlicensed Software'
'\\Software\Licensed Software')
#Gets folder contents of directories at a depth of three.
Foreach($Directory in $Directories)
{
$Path = Get-ChildItem $Directory -Recurse -Depth 3| ?{ $_.PSIsContainer } | Select-Object FullName;
$Software += $Path
}
#Gets user string.
$target = Read-Host -Prompt 'Input a software name or part of a name. ("Exit" to quit)';
#Finds matches and adds them to Links.
while ($target -ne "exit")
{
$count = 0;
$Links = New-Object Collections.Generic.List[string];
Foreach ($line in $Software)
{
if($line -like "*$target*")
{
$Links.Add($line.FullName);
$count += 1;
}
#Stops code when results are greater than 100 entries.
if($count -gt 99)
{
Write-Output "Your search result yielded more than 100 entries. Try narrowing down your search."
Break
}
}
#Prints links.
ForEach($Link in $Links)
{
Write-Output $Link;
}
Write-Host `n$count" entries found`n";
#Asks users if they would like to continue.
$target = Read-Host -Prompt 'Input a software name or part of a name ("Exit" to quit)';
}
#Exits Program
Write-Host "Press any key to continue ...";
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
This is a quite simple way you could use to select a folder an open it, it will display an Out-GridView until you cancel or close the grid and open the folder once you make a selection. If you're looking for something better looking and more complex you would have to code your own WinForm app.
$directories = Get-ChildItem $env:USERPROFILE -Directory |
Select-Object Name, FullName
do
{
$selection = $directories |
Out-GridView -PassThru -Title 'Choose a Folder'
if($selection){ Invoke-Item $selection.FullName }
} while($selection)
You could make a HTML File:
Add-Type -AssemblyName System.Web
[System.Web.HttpUtility]::HtmlDecode((gci | select Name,#{n='Fullpath'; e={(("<a href='file://" +$_.fullname+"'>"+$_.Fullname+'</a>' ))}} | ConvertTo-Html)) | out-file c:\file.html
iex c:\file.html

Loop through folders/files and replace word

Trying to loop through a folders/subfolders that contains multiple html files to find a specific string/word.
Once the powershell script finds it - it will list which files contains the matching words.
Maybe a prompt for the user to replace the word or just set a variable such as $replaceword = 'test2'
$match = "THIS IS A TEST"
#$replacement = Read-Host "Please enter a solution name"
$files = Get-ChildItem -path C:\Users\testfolder -filter *THIS IS A TEST* -Recurse
$files
Sort-Object -Descending -Property { $_.FullName }
#Rename-Item -newname { $_.name -replace $match, $replacement } -force
$files = Get-ChildItem -path C:\Users\testfolder -include *.html -Recurse
foreach($file in $files)
{
((Get-Content $file.fullname) -creplace $match)
read-host -prompt "Done! Press any key to close."
}
You can do something like the following, which will replace matching content of .html files in the C:\Users\testfolder directory structure:
$matchString = 'THIS IS A TEST'
$replaceString = 'New String'
$files = Get-ChildItem -Path 'C:\Users\testfolder' -filter *.html -Recurse
foreach ($file in $files) {
$matchFound = $false
$output = switch -regex -file $file.fullname {
$matchString { $_ -replace $matchString,$replaceString
$matchFound = $true
}
default { $_ }
}
if ($matchFound) {
$output | Set-Content $file.fullname
}
}
If you really want an interactive prompt to replace the string, you can move the $replaceString to a different place within the script logic and add the Read-Host command.
Explanation:
The top of the script contains $matchString (the case-sensitive string you want to match) and $replaceString (the replacement string).
Get-ChildItem uses -Filter to find all files that end with the HTML extension. The -Recurse switch is used to search all subdirectories from the path.
The switch statement is used in favor of Get-Content because it typically performs faster. We only want to overwrite a file when a match is found. $matchFound begins as $false and will become $true when a match is found.
-creplace is used here since your source code used it. -creplace performs a case-sensitive regex match and replace.
Interactive Alternative:
For an interactive element, you can ask the user to enter the replacement string each time a match is found. This simply moves the $replaceString assignment inside of the switch statement and lists the file that contains the match.
$matchString = 'THIS IS A TEST'
$files = Get-ChildItem -Path 'C:\Users\testfolder' -filter *.html -Recurse
foreach ($file in $files) {
$matchFound = $false
$output = switch -regex -file $file.fullname {
$matchString { $replaceString = Read-Host -Prompt "$($file.fullname) contains '$matchString'. Enter your replacement string"
$_ -replace $matchString,$replaceString
$matchFound = $true
}
default { $_ }
}
if ($matchFound) {
$output | Set-Content $file.fullname
}
}
What about something like this, it's a simpler example:
$files = Get-ChildItem -Path c:\path\here -Recurse
$matchString = 'THIS IS A TEST'
$replacementString = 'replace'
foreach ($i in $files){
#Check its a file
if (!$i.PSIsContainer){
#Check the name contains the $matchString
if ($i.Name -match $matchString){
Set-Content -Path $i.FullName -Value $replacementString -Confirm:$true
}
}
}
Let me know if I've misunderstood what you want :)

Identify Empty Folders

I would like to identify a specific empty folder in our user profiles.
I have a text file containing all of our user names that I want the script to refer to. The script will loop each user directory and either output to file or screen and say if the directory is empty. Hidden files do not have to count!
Something similar
FOR /F %U IN (C:\UserList\UserList.TXT) DO *Find and List Empty Folder* \\Server\Share\%U\Target_Folder
Powershell solutions welcome!
This article on Technet provides the following Powershell code snippet to identify all empty folders:
$a = Get-ChildItem C:\Scripts -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName
Replace "C:\Scripts" with the root folder you want to search.
Update:
The following script will get you in the ballpark.
$content = Get-Content C:\Temp\FolderList.txt
foreach ($line in $content)
{
Write-Host $line -NoNewline
$testObject = Test-Path -Path $line
if ($testObject)
{
$folder = Get-Item -Path $line
$filesCount = $folder.GetFiles().Count
if ($filesCount.Equals(0))
{
Write-Host " - Empty folder"
}
else
{
Write-Host " - Contains files"
}
}
else
{
Write-Host " - Invalid path"
}
}

Powershell Script to search for credit card numbers in a folder

I am using the below script to search for credit card numbers inside a folder that contains many subfolders:
Get-ChildItem -rec | ?{ findstr.exe /mprc:. $_.FullName }
| select-string "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}"
However, this will return all instances found in every folder/subfolder.
How can I amend the script to skip the current folder on the first instance found? meaning that if it finds a credit card number it will stop processing the current folder and move to the next folder.
Appreciate you answers and help.
Thanks in advance,
You could use this recursive function:
function cards ($dir)
Get-ChildItem -Directory $dir | % { cards($_.FullName) }
Get-ChildItem -File $dir\* | % {
if ( Select-String $_.FullName "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}" ) {
write-host "card found in $dir"
return
}
}
}
cards "C:\path\to\base\dir"
It'll keep going through subdirectories of the top level directory you specify. Whenever it gets to a directory with no subdirectories, or its been through all the subdirectories of the current directory, it'll start looking through the files for the matching regex, but will bail out of the function when the first match is found.
So really what you want is the first file in every folder that has a credit card number in the contents.
Break it into two parts. Get a list of all your folders, recursively. Then, for each folder, get the list of files, non-recursively. Search each file until you find one that matches.
I don't see any easy way to do this with pipes alone. That means more traditional programming techniques.
This requires PowerShell 3.0. I've eliminated ?{ findstr.exe /mprc:. $_.FullName } because all I can see that it does is eliminate folders (and zero length files) and this already handles that.
Get-ChildItem -Directory -Recurse | ForEach-Object {
$Found = $false;
$i = 0;
$Files = $_ | Get-ChildItem -File | Sort-Object -Property Name;
for ($i = 0; ($Files[$i] -ne $null) -and ($Found -eq $false); $i++) {
$SearchResult = $Files[$i] | Select-String "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}";
if ($SearchResult) {
$Found = $true;
Write-Output $SearchResult;
}
}
}
Didn't have the time to test it fully, but I thought about something like this:
$Location = 'H:\'
$Dirs = Get-ChildItem $Location -Directory -Recurse
$Regex1 = "[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}"
$Regex2 = "[456][0-9]{15}"
Foreach ($d in $Dirs) {
$Files = Get-ChildItem $d.FullName -File
foreach ($f in $Files) {
if (($f.Name -match $Regex1) -or ($f.Name -match $Regex2)) {
Write-Host 'Match found'
Return
}
}
}
Here is another one, why not, the more the merrier.
I'm assuming that your Regex is correct.
Using break in the second loop will skip looking for a credit card in the remaining files if one is found and continue to the next folder.
$path = '<your path here>'
$folders = Get-ChildItem $path -Directory -rec
foreach ($folder in $folders)
{
$items = Get-ChildItem $folder.fullname -File
foreach ($i in $items)
{
if (($found = $i.FullName| select-string "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}") -ne $null)
{
break
}
}
}
I think the intention was to look inside each file for the PII data right?
If so, you need to open the load the file and search each line. The code you posted will only run a regex on the name of the file.

Only recurse subfolder x numbers of levels using PowerShell

The PowerShell script below will list out all shared folders (excluding hidden shared folders), then list out all sub-folders and finally get the ACL information of each of them and export to a CSV file.
However, I'm trying to set the limit of the sub-folder it can drill into. For example, if I set it to 3, the script will get the ACL information of first three sub-folders. How can I do this?
Input:
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1\subfolder11\subfolder12\subfolder13\subfolder14
path=\\server\sharefolder2
Expected result:
path=\\server\sharefolder0
path=\\server\sharefolder0\subfolder01
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1
path=\\server\sharefolder1\subfolder11
path=\\server\sharefolder1\subfolder11\subfolder12
path=\\server\sharefolder2
This is the code:
$getSRVlist = Get-Content .\server.txt
$outputDirPath=".\DirPathList.txt"
$outputACLInfo=".\ACLInfo.CSV"
$header="FolderPath,IdentityReference,Rights"
Del $outputACLInfo
add-content -value $header -path $outputACLInfo
foreach ($readSRVlist in $getSRVlist)
{
foreach ($readShareInfoList in $getShareInfoList=Get-WmiObject Win32_Share
-computerName $readSRVlist | Where {$_.name -notlike "*$"} | %{$_.Name})
{
foreach ($readDirPathList in
$getDirPathList=get-childitem \\$readSRVlist\$readShareInfoList -recurse
| where {$_.PSIsContainer})# | %{$_.fullname})
{
$getACLList=get-ACL $readDirPathList.fullname | ForEach-Object
{$_.Access}
foreach ($readACLList in $getACLList)
{
$a = $readDirPathList.fullname + "," +
$readACLList.IdentityReference + "," + $readACLList.FileSystemRights
add-content -value $a -path $outputACLInfo
}
}
}
}
Recursion is your friend. Try this:
$maxDepth = 3
function TraverseFolders($folder, $remainingDepth) {
Get-ChildItem $folder | Where-Object { $_.PSIsContainer } | ForEach-Object {
if ($remainingDepth -gt 1) {
TraverseFolders $_.FullName ($remainingDepth - 1)
}
}
}
TraverseFolders "C:\BASE\PATH" $maxDepth
Edit: Now I see what you mean. For checking the first three parent folders of a given path try this:
$server = "\\server\"
$path = ($args[0] -replace [regex]::escape($server), "").Split("\\")[0..2]
for ($i = 0; $i -lt $path.Length; $i++) {
Get-ACL ($server + [string]::join("\", $path[0..$i])
}
In newer version of powershell one can use -DEPTH parameter,
One liner can help-
get-childitem -path \\server\folder -Depth 2 -Directory | Select-object -Property Name, Fullname
It will search for 2 nested folders and will provide folder name and full path of that particular folder. Tested in version- PSVersion 5.1.17134.858