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

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

Related

Using Variables with Directories & Filtering

I'm new to PowerShell, and trying to do something pretty simple (I think). I'm trying to filter down the results of a folder, where I only look at files that start with e02. I tried creating a variable for my folder path, and a variable for the filtered down version. When I get-ChildItem for that filtered down version, it brings back all results. I'm trying to run a loop where I'd rename these files.
File names will be something like e021234, e021235, e021236, I get new files every month with a weird extension I convert to txt. They're always the same couple names, and each file has its own name I'd rename it to. Like e021234 might be Program Alpha.
set-location "C:\MYPATH\SAMPLE\"
$dir = "C:\MYPATH\SAMPLE\"
$dirFiltered= get-childItem $dir | where-Object { $_.baseName -like "e02*" }
get-childItem $dirFiltered |
Foreach-Object {
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
get-childitem $dirFiltered | rename-item -newname { $name + ".txt"}
}
There are a few things I can see that could use some adjustment.
My first thought on this is to reduce the number of places a script has to be edited when changes are needed. I suggest assigning the working directory variable first.
Next, reduce the number of times information is pulled. The Get-ChildItem cmdlet offers an integrated -Filter parameter which is usually more efficient than gathering all the results and filtering afterward. Since we can grab the filtered list right off the bat, the results can be piped directly to the ForEach block without going through the variable assignment and secondary filtering.
Then, make sure to initialize $name inside the loop so it doesn't accidentally cause issues. This is because $name remains set to the last value it matched in the if/elseif statements after the script runs.
Next, make use of the fact that $name is null so that files that don't match your criteria won't be renamed to ".txt".
Finally, perform the rename operation using the $_ automatic variable representing the current object instead of pulling the information with Get-ChildItem again. The curly braces have also been replaced with parenthesis because of the change in the Rename-Item syntax.
Updated script:
$dir = "C:\MYPATH\SAMPLE\"
Set-Location $dir
Get-ChildItem $dir -Filter "e02*" |
Foreach-Object {
$name = $null #initialize name to prevent interference from previous runs
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
if ($name -ne $null) {
Rename-Item $_ -NewName ($name + ".txt")
}
}

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 Return only folders that exist

We are deleting a load of XP profiles to save disk space. Some profile folders for XP exist as well as those for the same user with the .V2 for windows 7. Id like to use powershell to return only those XP profiles that exist.
Here is my code so far
$path = "\\server01\profiles"
#Get User Folder names into variable for ForEach Loop
$UserFolders = get-childitem -path $path\*.V2 | where-object {$_.Psiscontainer -eq "True"} |select-object name
#Loop through folders in Directory
foreach ($UserFolder in $UserFolders){
#remove the last .V2 from the folder name
$UserFolder = $UserFolder.name.substring(0,$UserFolder.name.length-3)
write-output $path\$userfolder
test-path $path\$userfolder #returns True or false
}
I just cant get the last bit to work. How can I only display only those folders that exist (return True)
You just need to rearrange things so that Write-Output only occurs if Test-Path validates that a path exists. I won't add anything new to your code except for an if statement, as an example:
foreach ($UserFolder in $UserFolders){
$UserFolder = $UserFolder.name.substring(0,$UserFolder.name.length-3)
if (test-path $path\$userfolder)
{
# Code at this level will only be executed if Test-Path was true.
write-output $path\$userfolder
}
}
So by placing write-output into the scope of the if statement, we ensure that it will only be executed if Test-Path is true.

How to use an array in a zip function using powershell?

I am still pretty new to scripting and "programming" at all. if you miss any information here let me know.
This is my working zip function:
$folder = "C:\zipthis\"
$destinationFilePath = "C:\_archive\zipped"
function create-7zip{
param([string] $folder,
[String] $destinationFilePath)
write-host $folder $destinationFilePath
[string]$pathToZipExe = "C:\Program Files (x86)\7-Zip\7zG.exe";
[Array]$arguments = "a", "-tzip", "$destinationFilePath", "$folder";
& $pathToZipExe $arguments;
}
Get-ChildItem $folder | ? { $_.PSIsContainer} | % {
write-host $_.BaseName $_.Name;
$dest= [System.String]::Concat($destPath,$_.Name,".zip");
(create-7zip $_.FullName $dest)
}
create-7zip $folder $destinationFilePath
now I want him to zip special folders which I already sorted out :
get-childitem "C:\zipme\" | where-Object {$_.name -eq "www" -or $_.name -eq "sql" -or $_.name -eq "services"}
This small function finds the 3 folders I need called www, sql and services. But I didn't manage to insert this into my zip function, so that exactly this folders are zipped and put into C:\_archive\zipped
Because a string is used instead of an array, he tried always to look for a folder called wwwsqlservice which is not there. I tried to put an array using #(www,sql,services) but i had no success, so whats the right way, if there is one?
It should compatible with powershell 2.0, no ps3.0 cmdlets or functions please.
thanks in advance!
Here's a really simple example of what you want to do, removed from the context of your function. It assumes that your destination folders already exist (You can just use Test-Path and New-Item to create them if they don't), and that you're using 7z.exe.
$directories = #("www","sql","services")
$archiveType = "-tzip"
foreach($dir in $directories)
{
# Use $dir to update the destination each loop to prevent overwrites!
$sourceFilePath = "mySourcePath\$dir"
$destinationFilePath = "myTargetPath\$dir"
cmd /c "$pathToZipExe a $archiveType $destinationFilePath $sourceFilePath"
}
Overall it looks like you got pretty close to a solution, with some minor changes needed to support the foreach loop. If you're confident that create-7zip works fine for a single folder, you can substitute that for the cmd /c line above. Here's a listing of some handy example usages for 7zip on the command line.

Using PowerShell to move a matching set of files with same name but different extensions to two different destinations

I would like to use PowerShell to move a matching name set of files (1 job file and 1 trigger file both havening the same name just different extensions) from one directory to another. See example below.
Source directory contains job1.zip, job1.trg, job2.zip, and job2.trg. I would like to take matching job names job1.zip and job1.trg and move it to dest1folder, only if it is empty, if not move it to dest2folder. Then loop back to perform the same logic for job2.zip and job2.trg. One thing I also have to take into consideration is the Source directory may only contain job1.zip waiting for job1.trg to be transferred. I am a newbie to PowerShell and blown hours on trying to get it working with no success. Is it possible?
This is what I have so far. I get the files to move to each destination folder using IF logic, but it moves all of the files in the source directory.
$doirun = (get-childItem "d:\ftproot\pstest\").Count
$filecount = (get-childItem "d:\ftproot\ps2\").Count
if ($doirun -le 1) {exit}
$dir = get-childitem "d:\ftproot\pstest\" | Where-Object {($_.extension -eq ".zip") -or ($_.extension -eq ".trg")}
foreach ($file in $dir)
{
if ($filecount -le 2) {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps2\"}
else {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps3\"}
}
Not tested extensively, but I believe this should work:
$jobs = gci d:\ftproot\pstest\* -include *.zip,*.trg |
select -expand basename | sort -unique
$jobs |foreach-object {
if (test-path d:\ftproot\pstest\$_.zip -and test-path d:\ftproot\pstest\$_.trg){
if (test-path d:\ftproot\pstest\ps2\*){
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps3
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps3
}
else {
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps2
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps2
}
}