Powershell - Declaring paths with "$_.pdf" in it - powershell

As title says.
I have several if-statements with "Test-Path" in it so it'd be much better if I just declare the variables right away.
$jobs = Get-ChildItem d:\Path\* -recurse -include *.pdf,*.idx |
Select-Object -expand basename |
Sort-Object
$jobs | foreach-object{
if ((test-path d:\Path\$_.idx) -and (test-path d:\path\$_.pdf)){
move-item d:\Path\$_.idx d:\Path
move-item d:\path\$_.pdf d:\Path
}
else {
....
}
}
The file name is varying all the time. That's why I'm using "$_.pdf" for example. This is just a simple example. Add even one more file extension to this and yeah. The code functions like "If pdf and idx basenames are true/equal, move them"
This works completely fine, it's just that putting in the paths for each if statement makes it more confusing.
Any help would be appreciated. Thanks in advance!

Test-Path can take an array of items to be tested. When used so, it will return an array of boolean values. Should that array contain at least one $false, there is at least one missing file. If there isn't, all the files are present.
An example is like so,
# Create a few test files
set-content foo.idx ''
set-content foo.dpf ''
set-content foo.pub ''
# Instead of foo, you'd populate $s with file's basename
# and use a foreach loop
$s ="foo"
# Test if the trio exists. Note variable $s that contains the basename
if( (Test-Path #(".\$s.idx", ".\$s.dpf", ".\$s.pub")) -contains $false){
"nay" # Go here if there was at least one false
}else{
"aye" # go here if all were true
}
# Output
aye
# Change one extension, so trio doesn't exist
if( (Test-Path #(".\$s.idx", ".\$s.dpf", ".\$s.bub")) -contains $false) {
"nay" # Go here if there was at least one false
}else{
"aye" # go here if all were true
}
# Output
nay

I'm dumb.
I just have to put $path\$_.pdf there. thats it

Related

How can I tell if a specified folder is in my PATH using PowerShell?

How can I tell if a specified folder is in my PATH using PowerShell?
A function like this would be great:
function FolderIsInPATH($Path_to_directory) {
# If the directory is in PATH, return true, otherwise false
}
Going off this question, you don't need a function for this but can retrieve this with $Env:Path:
$Env:Path -split ";" -contains $directory
The -contains operator is case-insensitive which is a bonus. It could be useful placing this in a function to ensure trailing slashes are trimmed, but that's uncommon:
function inPath($directory) {
return ($Env:Path -split ';').TrimEnd('\') -contains $directory.TrimEnd('\')
}
There's a bunch of answers that do a $path.Split(";") or $path -split ";" that will probably be fine for 99.9% of real-world scenarios, but there's a comment on the accepted answer on a similar question here by Joey that says:
Will fail with quoted paths that contain semicolons.
Basically, it's a bit of an edge case, but this is a perfectly valid PATH on Windows:
c:\temp;"c:\my ; path";c:\windows
so here's a hot mess of code to address that...
function Test-IsInPath
{
param( [string] $Path, [string] $Folder )
# we're going to treat the path as a csv record, but we
# need to know how many columns there are so we can create
# some fake header names. this might give a higher count
# than the real value if there *are* quoted folders with
# semicolons in them, but that's not really an issue
$columnCount = $Path.Length - $Path.Replace(";","").Length
# generate the list of column names. the actual names
# don't matter - it's just so ConvertFrom-Csv treats our
# PATH as a data row instead of a header row
$headers = 0..$columnCount
# parse the PATH as a csv record using ";" as a delimiter
$obj = $path | ConvertFrom-Csv -header $headers -delimiter ";"
# extract an array of all the values (i.e. folders)
# in the record we just parsed
$entries = $obj.psobject.properties.value
# check if the folder we're looking for is in the list
return $entries.Contains($Folder)
}
Whether this is a "better" answer than the simple split approach depends on whether you expect to have quoted folders that contain semicolons in your PATH or not :-)...
Example usage:
PS C:\> Test-IsInPath -Path $env:PATH -Folder "c:\temp"
False
PS C:\> Test-IsInPath -Path "c:\temp;`"c:\my ; path`";c:\windows" -Folder "c:\temp"
True
PS C:\> Test-IsInPath -Path "c:\temp;`"c:\my ; path`";c:\windows" -Folder "c:\my ; path"
True
Note: what this still doesn't solve is paths that end (or don't end) with a trailing "\" - e.g. testing for C:\temp when the PATH contains C:\temp\ and vice versa.
I would go for something like this
function FolderIsInPATH {
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$your_searched_folder
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Path
)
$Folders = Get-Childitem -Path $Path -Directory
foreach ($Folder in $Folders) {
if ($Folder.Name -eq $your_searched_folder) {
##Folder found
} else {
##folder not found
}
}
}
You can get your PATH using [Environment]::GetEnvironmentVariables()
[Environment]::GetEnvironmentVariables()
Or if you want to get the user environment variables:
[Environment]::GetEnvironmentVariables("User")
Next, get the PATH variable:
$Path = [Environment]::GetEnvironmentVariables().Path # returns the PATH
Then check if the specified folder is in your PATH:
$Path.Contains($Path_to_directory + ";")
The function put together:
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.Contains($Path_to_directory + ";")
}
However, this function is case-sensitive. You can use String.ToLower() to make it not case-sensitive.
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.ToLower().Contains($Path_to_directory.ToLower() + ";")
}
Now call your function like this:
FolderIsInPath("C:\\path\\to\\directory")
Note that the path must be absolute.
As pointed out in mclayton's comment, this function won't work for the last path variable. To address this issue, simply add a ; to the end of the path. Your function would now look like this.
function FolderIsInPath($Path_to_directory) {
return [Environment]::GetEnvironmentVariables("User").Path.ToLower() + ";".Contains($Path_to_directory.ToLower() + ";")
}

How do I get the path of a directory where a file SQL backup is missing?

The task is to track the execution of SQL backups. One of the tracking mechanisms is to browse the directory where the backups are saved and if the file is missing, you can send an email with the full address of the directory where the file is missing. This is what I am trying to implement with Powershell script, but for some reason my script doesn't work and doesn't give me the information I need.
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)}
$fileExists = Test-Path $list
If ($fileExists)
{
#Do Nothing
}
Else
{
$list | Select DirectoryName
}
Can anyone help?
I suppose what you need is to test each file or path individually. You take Get-ChildItem with recurse, so it returns multiple files and stores them in $list.
If you do something like
Foreach ($item in $list) {
$fileexists = Test-Path $item
If ($fileexists -eq $false) {
do something }
}
You should be good to go. This would cycle through all items and does whatever you need to be done. If you compare against $false, you wouldn't need the else statement, and you could also just put "Test-Path" into the if-statement like
If (Test-Path $item -eq $false) {}
Edit: Sorry I accidentally posted the answer before finishing it lol
Also, as stackprotector correctly points out, Get-ChildItem can only retrieve items that exist, because how should it detect missing files.
If you're wanting to check for something that is missing or doesn't exist, you need to start with a known condition, e.g.: either the server names or expected file or directory names.
If you know that, then you can create a static list (or dynamically query a list from Active Directory for your SQL servers or something (assuming the backup file names correspond to the server names)) and then check the files that were created and output the missing ones for triage.
Here is a modification to your script (essentially the opposite of what you did) that might point you in the right direction:
## List of expected files
$ExpectedFiles = #(
'File1.bak',
'File2.bak',
'File3.bak',
'File4.bak'
'...'
)
## Get a list of created files
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)} | Select -ExpandProperty Name
## Check whether each expected file exists in the array of backups that actually were created
foreach ($file in $ExpectedFiles) {
if (-not(Test-Path $list)) {
"$($file) is missing!"
}
}

Deleting CSV the entire row if text in a column matches a specific path or a file name

I'm new to Powershell so please try to explain things a little bit too if you can. I'm trying to export the contents of a directory along with some other information in a CSV .
The CSV file contains information about the files however, I just need to match the FileName column (which contains the full path). If it's matched, I need to delete the entire row.
$folder1 = OldFiles
$folder2 = Log Files\January
$file1 = _updatehistory.txt
$file2 = websites.config
In the CSV file, if any of these is matched, the entire row must be deleted. The CSV file contains FileName in this manner:
**FileName**
C:\Installation\New Applications\Root
I've tried doing this:
Import-csv -Path "C:\CSV\Recursion.csv" | Where-Object { $_.FileName -ne $folder2} | Export-csv -Path "C:\CSV\RecursionUpdated.csv" -NoTypeInformation
But it's not working out. I would really appreciate help here.
It looks like you want to match only parts of the full path, so you should use -like or -match operators (or their negated variants) which can do non-exact matching:
$excludes = '*\OldFiles', '*\Log Files\January', '*\_updatehistory.txt', '*\websites.config'
Import-csv -Path "C:\CSV\Recursion.csv" |
Where-Object {
# $matchesExclude Will be $true if at least one exclude pattern matches
# against FileName. Otherwise it will be $null.
$matchesExclude = foreach( $exclude in $excludes ) {
# Output $true if pattern matches, which will be captured in $matchesExclude.
if( $_.FileName -like $exclude ) { $true; break }
}
# This outputs $true if the filename is not excluded, thus Where-Object
# passes the row along the pipeline.
-not $matchesExclude
} | Export-csv -Path "C:\CSV\RecursionUpdated.csv" -NoTypeInformation
This code makes heavily use of PowerShell's implicit output behaviour. E. g. the literal $true in the foreach loop body is implicit output which will be automatically captured in $matchesExclude. If it were not for the assignment $matchesExclude = foreach ..., the value would have been written to the console instead (if not captured somewhere else in the callstack).

Counting Folder Depth with PowerShell

1. Code Description alias how it is intended to work
User enters a path to a directory in PowerShell. Code checks if any folder within the declared directory contains no data at all. If so, the path of any empty folder will be shown on the prompt to the user and eventually removed from the system.
2. The Issue alias what I am struggling with
The code I just wrote doesn't count the depth of a folder hierarchy as I would expect (the column in the output table is blank). Besides that, the program works okay - I've still got to fix the issue where my code removes empty parent directories first and child directories later, which of course will cause an error in PowerShell; for instance, take
C:\Users\JohnMiller\Desktop\Homework
where Homework consists of Homework\Math\School Project and Homework\Computer Science\PowerShell Code. Note that all directories are supposed to be empty with the exception of PowerShell Code, the folder containing this script. (Side note: A folder is considered empty when no file dwells inside. At least that's what my code is based on for now.)
3. The Code
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,#{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
The depth-count problem:
Your code references a .DirectoryName property in the calculated property passed to Select-Object, but the [System.IO.DirectoryInfo] instances output by Get-ChildItem have no such property. Use the .FullName property instead:
$allEmptyFolders |
Select-Object FullName,#{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
Eliminating nested empty subfolders:
To recap your problem with a simple example:
If c:\foo is empty (no files) but has empty subdir. c:\foo\bar, your code outputs them both, and if you then delete c:\foo first, deleting c:\foo\bar next fails (because deleting c:\foo also removed c:\foo\bar).
If you eliminate all nested empty subdirs. up front, you not only declutter what you present to the user, but you can then safely iterative of the output and delete one by one.
With your approach you'd need a 2nd step to eliminate the nested empty dirs., but here's a depth-first recursive function that omits nested empty folders. To make it behave the same way as your code with respect to hidden files, pass -Force.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(#(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
Tips regarding your code:
Get-ChildItem -Directory is available in PSv3+, which is not only shorter but also more efficient than Get-ChildItem | .. Where { $_.PSisContainer -eq $True }.
Use Write-Host instead of [Console]::WriteLine
[System.Convert]::ToBoolean($answer) only works with the culture-invariant string literals 'True' and 'False' ([bool]::TrueString and [bool]::FalseString, although case variations and leading and trailing whitespace are allowed).

Powershell: Move Items Based on Destination from Hashtable

I'm attempting to write a PowerShell script to move files from one directory to another based on a few conditions. For example:
An example of a file name: testingcenter123456-testtype-222-412014.pdf.
The script should look for "testingcenter123456" before the first dash ("-") and then refer to a hash table for a matching key. All the files follow the format shown above.
Once its finds that key, it should use that key's corresponding value (example: "c:\temp\destination\customer7890") as the destination file path and copy the file there.
I looked around StackOverflow and found a few Q&As that seemed to answer parts of similar questions but the fact that I'm very new to this has led to the script I pieced together not working at all.
Here's what I have so far:
$hashTable = ConvertFrom-StringData ([IO.File]::ReadAllText("c:\temp\filepaths.txt"))
$directory = "c:\temp\source"
Get-ChildItem $directory |
where {!($_.PsIsContainer)} |
Foreach-Object {
Foreach ($key in $hashTable.GetEnumerator()){
if ($_.Name.Substring(0,$_.Name.IndexOf("-")) -eq $key.Name){
Copy-Item -Path $_.fullname -Destination $key.Value
}
}
}
If anyone can help me get un-stuck and hopefully learn a little something about PowerShell in the process, I'd appreciate it.
Honestly, I'm not seeing why this shouldn't work. It would be helpful if you told us which line was generating an error.
Foreach ($key in $hashTable.GetEnumerator()) {
if ($_.Name.Substring(0,$_.Name.IndexOf("-")) -eq $key.Name) {
Copy-Item -Path $_.fullname -Destination $key.Value
}
}
That said, you're missing the point of using hashtable by looping through its entries, manually matching on key. With a hashtable, you don't need to loop e.g.
$hashTable = ConvertFrom-StringData ([IO.File]::ReadAllText("c:\temp\filepaths.txt"))
Get-ChildItem c:\temp\source |
Where {!($_.PsIsContainer)} |
Foreach-Object {
$key = $_.Name.Substring(0,$_.Name.IndexOf("-"))
$val = $hashtable.$key
if ($val) {
$_ | Copy-Item -Dest $val -WhatIf
}
else {
Write-Warning "No entry for $key"
}
}