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.
Related
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!"
}
}
I would like to run a PowerShell script by right-clicking a .PS1 file -> Run with PowerShell. The issue is that the $srcRoot includes three different parent directories, which one, some, or none may exist of 'C:\parentfolder5.5\web\','C:\parentfolder7.0\web\', and/or 'C:\parentfolder8.0\web\'. However, running the script directly in a PowerShell terminal seems to work for the parent folders that do exist even though there are errors that pop up for the parent folders that do not exist.
The goal would be to continue running the script based on the parent directories that do exist, which currently it looks like the .PS1 file stops after looking at the first item in the $srcRoot list. Below is the code being worked on:
$filterLists = '*overview*', '*summary*', '*home*', '*floor*', '*flr*', '*level*', '*lvl*', '*roof*', '*basement*', '*first*', '*second*', '*third*', '*fourth*'
$srcRoot = 'C:\parentfolder5.5\web\','C:\parentfolder7.0\web\','C:\parentfolder8.0\web\'
$dstRoot = $MyInvocation.MyCommand.Path
$params = #{
Path = LiteralPath = $srcRoot |Where-Object { Test-Path -LiteralPath $_ -PathType Container }
Filter = 'views'
Recurse = $true
Directory = $true
}
# All folders under `$srcRoot` with name 'views'
$viewsFolders = Get-ChildItem #params #this line is where the issue
seems to start when Right-clicking -> Run with PowerShell
$params.LiteralPath = $viewsFolders.FullName
$params.Filter = 'graphics'
# All folders under `$viewsFolders` with name 'graphics'
$graphicsFolders = Get-ChildItem #params
$params.Remove('Directory')
$params.LiteralPath = $graphicsFolders.FullName
$params.File = $true # Only search for Files
$params.Force = $true
$params.Remove('Filter')
# All files under `$graphicsFolders`
foreach($file in Get-ChildItem #params)
{
# Where the file name contains one of these filters
foreach($filter in $filterLists)
{
if($file.Name -like $filter)
{
#$file
Copy-Item -Path $($file.FullName) -Destination $dstRoot
# if you want to stop at the first finding
# add `break` here
}
}
}
Help on this would be greatly appreciated!
You can use Where-Object to filter the list of paths. Use Test-Path to test whether each exists and is a directory path:
$params = #{
LiteralPath = $srcRoot |Where-Object { Test-Path -LiteralPath $_ -PathType Container }
Filter = 'views'
Recurse = $true
Directory = $true
}
# this will only attempt directory paths that actually exist now
$viewsFolders = Get-ChildItem #params
Note: Use of LiteralPath (instead of Path) above is intentional - using Path will cause PowerShell to attempt to expand wildcards like ?, * or [abc], whereas -LiteralPath only take exact file/folder names.
I have a PowerShell code where I want to check if in folder is file containing certain pattern.
If there is a file with certain pattern then I want to continue the work.
I have it as this:
$path = "c:\test\"
$Pattern = "1044"
$1044 = Get-ChildItem $Path | Where {$_.Name -match $Pattern}
$strFileName = "C:\test\" + $1044
if (Test-Path $strFileName) {
...
}
The issue is that the code keeps running even though the file with the pattern isn't there.
I printed the $1044 and it says
Can't find file
I printed also out the if condition and it says:
Can't find file True
That's why the code is running but I really don't understand why.
The statement $1044 = Get-ChildItem $Path | Where {$_.Name -match $Pattern} could return any number of results (zero, one, or more).
If it returns zero results the variable $1044 will be empty, so $strFileName = "C:\test\" + $1044 will evaluate to just C:\test\, which Test-Path will (correctly) report as existing.
If it returns more than one result he variable $1044 will contain a list of file or folder objects. Doing $strFileName = "C:\test\" + $1044 will mangle this list into a space-separated string of the file/folder names and append that to the base path (meaning you'll get something like C:\test\1044foo bar1044 1044_baz instead of C:\test\1044foo, C:\test\bar1044, C:\test\1044_baz). This path doesn't exist, so Test-Path will always evaluate to false in that situation.
Besides, you don't need to build $strFileName by string concatenation in the first place (unless your base path is different from the path used with Get-ChildItem), because the objects returned by Get-ChildItem already have a property with the full path (FullName). And you also don't need to test the paths for existence, because Get-ChildItem wouldn't have returned an item if it didn't exist.
Change your code to something like this:
$1044 = Get-ChildItem $Path | Where-Object {$_.Name -match $Pattern}
if ($1044) {
...
}
and it should do what you expect, because PowerShell evaluates $1044 to $false if it's empty and to $true if it isn't.
If your patern is not complicated, you can use test-path with wildcard, not necessary to use get-childitem
$Path="C:\test\*"
$Patern="*1044*"
if (Test-Path -Path $Path -Filter $Patern -PathType Leaf)
{
"File whith patern exist"
}
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).
I'm new to PowerShell and trying to teach myself it so I can further my career. I've come up with a script to help my business (and impress my boss!) that basically gets a list of all the profiles on the machines from "C:\Documents and Settings\All Users" and I want it to identify if the NTUser.dat file is older than 90 days. If true, then delete that profile folder.
I've had a look around and attempted to write my own script using bits adapted from examples on here and the internet. As far as I can tell it works but it's not detecting how old the NTUser.dat is on the PC and the result is always the same. I'll paste it below. Please keep in mind my code may not be perfect :)
Any help or advise anyone can give would be much appreciated.
$excludeFolders = #("Administrator","sysmgmt","Default User","All Users")
$FolderPath = "C:\Documents and Settings"
$CurrentDate = Get-Date
$LastModified = $CurrentDate.AddDays(-90)
$Folders = Get-Childitem –path $FolderPath |
Where {$_.psIsContainer} |
Where {$_.LastWriteTime -le "$LastModified"} |
Where {$excludeFolders -notcontains $_.Name}
$NTuserFolderPath = "C:\Documents and Settings\$Folders.name\NTUser.dat"
If ($Folders) {
foreach ($Folder in $Folders) {
If ($NTuserFolderPath.LastWriteTime -gt $LastModified) {
Write-Host "$($Folder.Name) needs removing"
} Else
{
Write-Host "Skipping $($Folder.Name)"
}
}
} Else {
Write-Host "There are no files to empty."
}
I see two issues both coming from this line:
$NTuserFolderPath = "C:\Documents and Settings\$Folders.name\NTUser.dat"
The property $Folders.name wont expand properly in a quoted string without having it in a subexpression.
Since this line is not inside the loop the variable $NTuserFolderPath will always be the same.
So your loop should look like this instead
foreach ($Folder in $Folders) {
$NTuserFolderPath = "C:\Documents and Settings\$($Folder.name)\NTUser.dat"
If ((Get-Item $NTuserFolderPath).LastWriteTime -gt $LastModified) {
Write-Host "$($Folder.Name) needs removing"
} Else {
Write-Host "Skipping $($Folder.Name)"
}
}
You should also use Test-Path to check if the file exists first.
While I cant find a reference for it right now I would like to mention that checking the date of the ntuser.dat is not the most reliable way to see if a user account is stale on the local machine. Nothing could change in the registry but the user still be active. So just be careful and test.