Powershell getting full path information - powershell

I have a directory called Videos. Inside this directory, are a bunch of sub directories of various cameras. I have a script that will check each of the various cameras, and delete recordings older than a certain date.
I am having a bit of trouble getting the full directory information for the cameras. I am using the following to get it:
#Get all of the paths for each camera
$paths = Get-ChildItem -Path "C:\Videos\" | Select-Object FullName
And then I loop through each path in $paths and delete whatever I need to:
foreach ($pa in $paths) {
# Delete files older than the $limit.
$file = Get-ChildItem -Path $pa -Recurse -Force | Where-Object { $_.PSIsContainer -and $_.CreationTime -lt $limit }
$file | Remove-Item -Recurse -Force
$file | Select -Expand FullName | Out-File $logFile -append
}
When I run the script, I am getting errors such as:
#{FullName=C:\Videos\PC1-CAM1}
Get-ChildItem : Cannot find drive. A drive with the name '#{FullName=C' does not exist.
At C:\scripts\BodyCamDelete.ps1:34 char:13
+ $file = Get-ChildItem -Path $pa -Recurse -Force | Where-Object { $_.PSIsCont ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (#{FullName=C:String) [Get-ChildItem], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
Is there a way to strip that #{FullName= off of the Path? I think that may be what the issue is.

In your case $pa is an object with a FullName property. The way you would access that would be this.
$file = Get-ChildItem -Path $pa.FullName -Recurse -Force | Where-Object { $_.PSIsContainer -and $_.CreationTime -lt $limit }
However it would just be simpler to change only this line and leave
$paths = Get-ChildItem -Path "C:\Videos\" | Select-Object -ExpandProperty FullName
-ExpandProperty will just return the string instead of the object that Select-Object was returning.

You are nearly there. What you want is the -ExpandProperty argument for Select-Object. This will return the value of that property, instead of a FileInfo object with one property, that property being FullName. This should resolve it for you:
$paths = Get-ChildItem -Path "C:\Videos\" | Select-Object -ExpandProperty FullName
Edit: Looks like Matt beat me to it by a minute.

Related

Powershell Find all empty folders and subfolders in a given Folder name

I´m trying to get a
a) list of all empty folders and subfolders if the folder is named "Archiv"
b) I´d like to delete all those empty folders. My current approch doesn´t check the subfolders.
It would be also great if the results would be exportet in a .csv =)
$TopDir = 'C:\Users\User\Test'
$DirToFind = 'Archiv'>$EmptyDirList = #(
Get-ChildItem -LiteralPath $TopDir -Directory -Recurse |
Where-Object {
#[System.IO.Directory]::GetFileSystemEntries($_.FullName).Count -eq 0
$_.GetFileSystemInfos().Count -eq 0 -and
$_.Name -match $DirToFind
}
).FullName
$EmptyDirList
Any ideas how to adjust the code? Thanks in advance
You need to reverse the order in which Get-ChildItem lists the items so you can remove using the deepest nested empty folder first.
$LogFile = 'C:\Users\User\RemovedEmptyFolders.log'
$TopDir = 'C:\Users\User\Test'
# first get a list of all folders below the $TopDir directory that are named 'Archiv' (FullNames only)
$archiveDirs = (Get-ChildItem -LiteralPath $TopDir -Filter 'Archiv' -Recurse -Directory -Force).FullName |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object -Property Length -Descending
# next, remove all empty subfolders in each of the $archiveDirs
$removed = foreach ($dir in $archiveDirs) {
(Get-ChildItem -LiteralPath $dir -Directory -Force) |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object #{Expression = {$_.FullName.Length}} -Descending |
ForEach-Object {
# if this folder is empty, remove it and output its FullName for the log
if (#($_.GetFileSystemInfos()).Count -eq 0) {
$_.FullName
Remove-Item -LiteralPath $_.FullName -Force
}
}
# next remove the 'Archiv' folder that is now possibly empty too
if (#(Get-ChildItem -LiteralPath $dir -Force).Count -eq 0) {
# output this folders fullname and delete
$dir
Remove-Item -LiteralPath $dir -Force
}
}
$removed | Set-Content -Path $LogFile -PassThru # write your log file. -PassThru also writes the output on screen
Not sure a CSV is needed, I think a simple text file will suffice as it's just a list.
Anyway, here's (although not the most elegant) a solution which will also delete "nested empty directories". Meaning if a directory only contains empty directorIS, it will also get deleted
$TopDir = "C:\Test" #Top level directory to scan
$EmptyDirListReport = "C:\EmptyDirList.txt" #Text file location to store a file with the list of deleted directorues
if (Test-Path -Path $EmptyDirListReport -PathType Leaf)
{
Remove-Item -Path $EmptyDirListReport -Force
}
$EmptyDirList = ""
Do
{
$EmptyDirList = Get-ChildItem -Path $TopDir -Recurse | Where-Object -FilterScript { $_.PSIsContainer } | Where-Object -FilterScript { ((Get-ChildItem -Path $_.FullName).Count -eq 0) } | Select-Object -ExpandProperty FullName
if ($EmptyDirList)
{
$EmptyDirList | Out-File -FilePath $EmptyDirListReport -Append
$EmptyDirList | Remove-Item -Force
}
} while ($EmptyDirList)
This should do the trick, should works with nested too.
$result=(Get-ChildItem -Filter "Archiv" -Recurse -Directory $topdir | Sort-Object #{Expression = {$_.FullName.Length}} -Descending | ForEach-Object {
if ((Get-ChildItem -Attributes d,h,a $_.fullname).count -eq 0){
$_
rmdir $_.FullName
}
})
$result | select Fullname |ConvertTo-Csv |Out-File $Logfile
You can do this with a one-liner:
> Get-ChildItem -Recurse dir -filter Archiv |
Where-Object {($_ | Get-ChildItem).count -eq 0} |
Remove-Item
Although, for some reason, if you have nested Archiv files like Archiv/Archiv, you need to run the line several times.

The specified path, file name, or both are too long Powershell

I have the below line in my powershell script in windows 7
$subFolderItems = Get-ChildItem $i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
The problem I get this error The specified path, file name, or both are too long in some of the files.
I looked into it and found some suggestions to add \\?\ before the file path I tried as below and it's not working any advice?
$Base = '\\?\'
$subFolderItems = Get-ChildItem $Base$i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
As you have commented, the path in $i.FullName is in UNC format (\\server\share\restofpath).
In that case, the prefix for long path should be \\?\UNC\ and the first two backslashes of the path itself need to be removed. In your case, it should be:
\\?\UNC\rackstation.mydom.com\main\sub.arch\mydom-customers\John_Marcus-123456
Using this prefix only works with the -LiteralPath parameter of Get-ChildItem.
Try
Get-ChildItem -LiteralPath ('\\?\UNC\' + $i.FullName.Substring(2))
For things like this, I always keep a small helper function handy:
function Add-LongPathPrefix([string] $Path) {
if ([string]::IsNullOrWhiteSpace($Path) -or $Path.StartsWith('\\?\')) { #'# nothing to do here
return $Path
}
if ($Path.StartsWith('\\')) {
# it's a UNC path like \\server\share\restofpath
return '\\?\UNC\' + $Path.Substring(2) #'# --> \\?\UNC\server\share\restofpath
}
else {
# it's a local path like X:\restofpath
return '\\?\' + $Path #'# --> \\?\X:\restofpath
}
}
Use it like this:
Get-ChildItem -LiteralPath (Add-LongPathPrefix $i.FullName)
P.S. For this you need to have Powershell 5.1 or higher
Hope that helps

Deleting Files Older than 30 Days with Powershell

I am trying to delete all files in a directory and all files in its sub-directories older than 30 days, leaving all folders intact. This question seems to have been asked to death online and I have this solution which i got from Stackoverflow:
$limit = (Get-Date).AddDays(-30)
$path = "path-to"
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force
Now this works and it doesn't.
When I try this on certain directories it works fine and exits normally. But when I try it on others I get this error:
Get-ChildItem : The given path's format is not supported.
At C:path-to-whatever\ClearFiles.ps1:5 char:1
+ Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsCo ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ChildItem], NotSupportedException
+ FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetChildItemCommand
I assume this is because of the time format in $._CreationTime, I have tried to remove this but when I do it continually asks me if i really want to delete the following files because I have not specified the recursive parameter, which I have at the beginning.
Could anyone clear this up? And perhaps explain why it works on some directories and not others.
Cheers
I couldn't reproduce your issue with the following code, but I will explain how I did it with some error handling ideas.
Lets first compare the two variables.
$_.LastWriteTime = Last time the file was written to.
$_.CreationTime = Time the file was created or Copy and pasted.
Adding the Out-GridView with the Select statement will provide us with a list of files on that path. I have added the Name, Attributes, CreationTime, LastWriteTime, and Fullname.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { $_.CreationTime -lt $Date } | Select Name, Attributes, CreationTime, LastWriteTime, Fullname | Out-GridView
If you Run As Administrator you could see more files. Certain hidden directories requires Run As Administrator.
Remove-Item has a really nice option of -WhatIf. What if we decide to delete the folders and files. WhatIf option doesn't delete, but it will show you what would have been deleted. Great for testing.
Get-ChildItem -Path $path -Recurse | Where-Object { $_.CreationTime -lt $Date } | Remove-Item -Recurse -whatif
Lets put this into a working Function with some error handling:
Function Remove_FilesCreatedBeforeDate{
$Path="F:\ISO\"
$Date=(Get-Date).AddDays(-30)
$ValidPath = Test-Path $Path -IsValid
If ($ValidPath -eq $True) {
Write-Host "Path is OK and Cleanup is now running"
#Get-ChildItem -Path $path -Recurse | Where-Object { $_.CreationTime -lt $Date } | Select Name, Attributes, CreationTime, LastWriteTime, Fullname | Out-GridView
#Get-ChildItem -Path $path -Recurse | Where-Object { $_.CreationTime -lt $Date } | Remove-Item -Recurse -whatif
Get-ChildItem -Path $path -Recurse | Where-Object { $_.CreationTime -lt $Date } #| Remove-Item -Recurse -Verbose
}
Else {Write-Host "Path is not a ValidPath"}
}
Remove_FilesCreatedBeforeDate
You only see the Warning\Confirm menu when you are about to delete a folder structure. What a lot of people fail to understand is the -Force option only removes hidden files and read-only files. We will want to use the -Recurse option to avoid this prompt, but note that it will delete everything.
I have commented out the Remove-Item for safety reasons.
#| Remove-Item -Recurse -Verbose
This works with $Path options like \\SERVER\$C\Directory\ or C:\Directory\. Let me know if you have any issues with this function.

Powershell -renaming a file after copying

I'm having ongoing trouble with a script I've written that is (supposed) to do the following.
I have one folder with a number of csv files, and I want to copy the latest file with the company name into another folder, and rename it.
It is in the current format:
21Feb17070051_CompanyName_Sent21022017
I want it in the following format:
CompanyName21022017
So I have the following powershell script to do this:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy latest Company CSV file ##
get-childitem -path $csv_path -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
copy-item -Destination $csv_dest
## Rename the file that has been moved ##
get-childitem -path $csv_dest -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 | rename-item $file -NewName {"Company" + $DateStamp + ".csv"}
The file seems to copy ok, but the rename fails -
Rename-Item : Cannot bind argument to parameter 'Path' because it is null.
At C:\Powershell Scripts\MoveCompanyFiles.ps1:20 char:41
+ select-object -last 1 | rename-item $file -NewName {"CompanyName" + $DateSt ...
I think it is something to do with the order in which powershell works, or the fact it can't see the .csv in the $file variable. There are other files (text files, batch files) in the destination, in case that affects things.
Any help in where I'm going wrong would be appreciated.
As wOxxOm answered, you need to remove $file from Rename-Item as it is not defined and the cmdlet already receives the inputobject through the pipeline.
I would also suggest that you combine the two operations by passing through the fileinfo-object for the copied file to Rename-Item. Ex:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy and rename latest Company CSV file ##
Get-ChildItem -Path $csv_path -Filter "*Company*.csv" |
Where-Object { -not $_.PSIsContainer } |
Sort-Object -Property CreationTime |
Select-Object -Last 1 |
Copy-Item -Destination $csv_dest -PassThru |
Rename-Item -NewName {"Company" + $DateStamp + ".csv"}
You can rename and copy in a single command. Just use Copy-Item Command and give new path and name as -Destination parameter value. It will copy and rename the file. You can find an example below.
$source_path = "c:\devops\test"
$destination_path = "c:\devops\test\"
$file_name_pattern = "*.nupkg"
get-childitem -path $source_path -Filter $file_name_pattern |
Copy-Item -Destination { $destination_path + $_.Name.Split("-")[0] + ".nupkg"}

Compare-Object Delete File if file does not exist on source

I have this PowerShell code that compares 2 directories and removes files if the files no longer exist in the source directory.
For example say I have Folder 1 & Folder 2. I want to compare Folder 1 with Folder 2, If a file doesn't exist anymore in Folder 1 it will remove it from Folder 2.
this code works ok but I have a problem where it also picks up file differences on the date/time. I only want it to pick up a difference if the file doesn't exist anymore in Folder 1.
Compare-Object $source $destination -Property Name -PassThru | Where-Object {$_.SideIndicator -eq "=>"} | % {
if(-not $_.FullName.PSIsContainer) {
UPDATE-LOG "File: $($_.FullName) has been removed from source"
Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue
}
}
Is there an extra Where-Object {$file1 <> $file2} or something like that.?
I am not sure how you are getting the information for $source and $destination I am assuming you are using Get-ChildItem
What i would do to eliminate the issue with date/time would be to not capture it in these variables. For Example:
$source = Get-ChildItem C:\temp\Folder1 -Recurse | select -ExpandProperty FullName
$destination = Get-ChildItem C:\temp\Folder2 -Recurse | select -ExpandProperty FullName
By doing this you only get the FullName Property for each object that is a child item not the date/time.
You would need to change some of the script after doing this for it to still work.
If I am not getting it wrong, the issue is your code is deleting the file with different time-stamp as compared to source:
Did you try -ExcludeProperty?
$source = Get-ChildItem "E:\New folder" -Recurse | select -ExcludeProperty Date
The following script can serve your purpose
$Item1=Get-ChildItem 'SourcePath'
$Item2=Get-ChildItem 'DestinationPath'
$DifferenceItem=Compare-Object $Item1 $Item2
$ItemToBeDeleted=$DifferenceItem | where {$_.SideIndicator -eq "=>" }
foreach ($item in $ItemToBeDeleted)
{
$FullPath=$item.InputObject.FullName
Remove-Item $FullPath -Force
}
Try something like this
In PowerShell V5:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 -file).Name
gci $yourdir2 -file | where Name -notin $filesnamedir1| remove-item
In old PowerShell:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 | where {$_.psiscontainer -eq $false}).Name
gci $yourdir2 | where {$_.psiscontainer -eq $false -and $_.Name -notin $filesnamedir1} | remove-item
If you want to compare files in multiple dir, use the -recurse option for every gci command.