Powershell: You cannot call a null-valued expression - powershell

Hello Stack Overflow Community,
at the moment I'm struggling with this code (it's not that beautiful):
$filepath = "C:\inetpub\logs\LogFiles"
$filearchivepath = "C:\inetpub\logs"
$daystoarchive = 1
$_ = "";
function create-7zip([String] $aDirectory, [String] $aZipfile){
#change the path where you downloaded the 7z exe
[string]$pathToZipExe = "C:\Users\kschweiger\Downloads\7za.exe";
[Array]$arguments = "a", "-tzip", "$aZipfile", "$aDirectory";
& $pathToZipExe $arguments;
}
#Create a new folder with the specific date
$ArchiveFolder = (Get-Date -Format dd.MM.yyyy) + " - Logs-Archive"
if(Test-Path "$filearchivepath\$ArchiveFolder"){
Write-Host "Folder already exists!"
}else{
New-Item -Path $filearchivepath -Name $ArchiveFolder -ItemType directory
}
#Save alle files older than X days into $Files
$Files = Get-ChildItem -Path $filepath -Recurse | where {$_.LastWriteTime -lt (Get-Date).AddDays(-$daystoarchive)}
#Copy/Move files and keep folder structure
foreach ($File in $Files){
$NewPath = $File.DirectoryName.Replace($filepath,"")
if (!(Test-Path "$filearchivepath\$ArchiveFolder\$NewPath"))
{
New-Item -Path "$filearchivepath\$ArchiveFolder\$NewPath" -ItemType Directory
}
$File | Copy-Item -Destination "$filearchivepath\$ArchiveFolder\$NewPath"
}
#Compress folder
if(Test-Path "$filearchivepath\$ArchiveFolder.zip"){
Write-Host "Archive-File already exists!"
}else{
#[IO.Compression.ZipFile]::CreateFromDirectory("$filearchivepath\$ArchiveFolder","$filearchivepath\$ArchiveFolder.zip")
create-7zip "$filearchivepath\$ArchiveFolder" "$filearchivepath\$ArchiveFolder.zip"
#Delete Folder
Remove-Item -Path "$filearchivepath\$ArchiveFolder" -Recurse -Force
}
The code works. but I also get a error message called:
You cannot call a null-valued expression
How can I resolve this?

Get-ChildItem by default returns files and folders. If you need only files, you should use -File. Otherwise, your $Files will contain folders too (as they have LastWriteTime property).
If you try to run .DirectoryName.Replace($filepath,"") on a folder, it'll return such error as you cannot run replacing on $null.
Update: for PowerShell 2.0 you can use | where { ! $_.PSIsContainer } (source)
How can I troubleshoot it by myself?
In your error you can see which line is broken:
$NewPath = $File.DirectoryName.Replace($filepath,"")
All you have to do to troubleshoot such situations is to list all the involved variables and check their values. You could do it like this:
$File
$File.DirectoryName
Pause
$NewPath = $File.DirectoryName.Replace($filepath,"")
Using Pause can be useful as it'll wait for you to press Enter before continuing.

Related

Archive files from a folder which are older than one week to a sub folder using powershell

I have tried below powershell script to move files older than 7 days from Newfolder to Archive_folder. The script is moving the entire path to the Archive_folder (means its creating folders \Users\529817\New folder in to Archive_folder and then copying files and not zipping the folder) , I need help in copying only files from NewFolder to Archive_folder and zip that folder.
$ArchiveYear = "2018"
$ArchiveMonth = "10"
$ArchiveDay = "10"
$SourcePath = "C:\Users\529817\New folder"
$TargetPath = "C:\Users\529817\New folder\Archive_folder"
$YourDirToCompress = "C:\Users\529817\New folder"
$ZipFileResult = "C:\Users\529817\New folder\Archive_folder\$ArchiveDay$ArchiveMonth.zip"
Get-ChildItem $YourDirToCompress -Directory |
#where { $_.Name -notin $DirToExclude} |
Compress-Archive -DestinationPath $ZipFileResult -Update
$Days = "7"
$LogPath = "C:Users\529817\Temp"
$Date = Get-Date -format yyyy-MM-dd_HH-mm
$TargetFolder = "$TargetPath\$Date"
$LogFile = "$LogPath\ArchiveLog-$date.txt"
$TargetZipFile = "$TargetPath\$Date.zip"
$Activity = "Move files older than $Days days from $SourcePath to $TargetFolder"
Write-Verbose $Activity
$OldFiles = Get-Childitem -Path $SourcePath -recurse | Where-Object {$_.LastWriteTime -lt (get-date).AddDays( - $days)}
$Total = $Oldfiles.Count
$Current = 0
$OldFiles | ForEach {
$Current ++
$Filename = $_.fullname
Write-Progress -Activity $Activity -Status $FileName -PercentComplete ($Current / $Total * 100)
$Split = $FileName -split '\\'
$DestFile = $split[1..($split.Length - 1)] -join '\'
$DestFile = "$TargetFolder\$DestFile"
Try {
$null = New-Item -Path $DestFile -Type File -Force
$Null = Move-Item -Path $FileName -Destination $DestFile -Force -ErrorAction:SilentlyContinue
"Successfully moved $filename to $targetfolder" | add-content $LogFile
}
Catch {
$Err = $_.Exception.Message
Write-Error $Err
"Error moving $filename`: $Err " | add-content $LogFile
}
}
You have two problems here:
Your zip file isn't going where you want it to go
Instead, all of the items which should be in the zip are going where the zip should go.
Let's figure out why this is happening so you can do what you need to get it working.
Problem 1
You have line 10 which looks like this:
Compress-Archive -DestinationPath $ZipFileResult -Update
This creates the Zip file but you don't actually do anything with this file, as in we don't see this $ZipFileResult used again in the code. This is why your zip file isn't showing up where you want it to be.
Problem 2
The end of this script has this big block where you are expressly copying the files to the directory,right where you don't want them.
Try {
$null = New-Item -Path $DestFile -Type File -Force
$Null = Move-Item -Path $FileName -Destination $DestFile -Force -ErrorAction:SilentlyContinue
"Successfully moved $filename to $targetfolder" | add-content $LogFile
}
If you only want to move the Zip file, then you can shorten this whole script. Delete everything from line 19 and on down, which begins with this line.
$OldFiles = Get-Childitem -Path $SourcePath -recurse | Where-Object {$_.LastWriteTime -lt (get-date).AddDays( - $days)}
And instead, add a Move-Item command to copy that $ZipFileResult file over to whichever directory you want it to go.
Finally, there are a number of lines which don't do anythign and can be deleted, like this line $TargetZipFile = "$TargetPath\$Date.zip"

copy files from folder and then delete some files with an exception

I have folder called Logfolder in C.
C:\LogFolder
it has multiple logs with name as follows
errorLogs.log
errorLogs.log.1
errorLogs.log.2
errorLogs.log.3
Transmitlogs.log
Transmitlogs.log.1
Transmitlogs.log.2
Transmitlogs.log.3
Transmitlogs.log.4
Transmitlogs.log.5
Receivelogs.log
Receivelogs.log.1
Receivelogs.log.2
Receivelogs.log.3
Receivelogs.log.4
Dataexchange.log
Dataexchange.log.1
and many other with the different name but with same extension like .log, .log.1 and so on.
I am interested in only above mention logs.
my goal is to copy this logs starting from log.1 upto log.10 or 20 all which exist and than
delete the original file with an exception .log and .log.1.
I have achieved following until now.
$logLocation = "C:LogFolder"
$tempLocation = "C:\Temp\Logs\"
$LogfileName = "errorLogs.log.", "Transmitlogs.log.","Receivelogs.log.","Dataexchange.log."
foreach ($element in $LogfileName)
{
$NewLogFileName = -join($element,"*")
Copy-Item -Path "$logLocation\$NewLogFileName" -Destination $tempLocation
}
I am able to copy all logs starting from .log.1 and all other which exist.
my problem is how can i delete those logs from original folder without deleting .log and .log.1
I have tried the following but not working.
foreach ($element in $LogfileName)
{
$deleteLogFileName = -join($element,"*")
Remove-Item –path "$logLocation\$deleteLogFileName" -exclude *.log, *.log.1
}
You can do that by selectively copy only file *.log.1 to the destination folder and move the others. That would save you removing files from the source location afterwards.
The thing that matters here most is to get a list of files that
have a numeric extension
have a basename like 'errorLogs.log', 'Transmitlogs.log', 'Receivelogs.log' or 'Dataexchange.log'
Try
$logLocation = "C:\LogFolder"
$tempLocation = "C:\Temp\Logs"
# if the destination folder does not exist yet, creatre it first
if (!(Test-Path -Path $tempLocation -PathType Container)) {
$null = New-Item -Path $tempLocation -ItemType Directory
}
# get an array of objects of the files where the extension ends in a numeric value
# and where the basename is either 'errorLogs.log', 'Transmitlogs.log', 'Receivelogs.log'
# or 'Dataexchange.log'.
$files = Get-ChildItem -Path $logLocation -Filter '*.log*' -File |
Where-Object {$_.Name -match '^(errorLogs|Transmitlogs|Receivelogs|Dataexchange)\.log\.\d+$' } |
Select-Object FullName, #{Name = 'Number'; Expression = {[int]($_.Name.Split(".")[-1])}}
foreach ($file in $files ) {
if ($file.Number -eq 1) {
# this file should be copied
Copy-Item -Path $file.FullName -Destination $tempLocation -Force
}
else {
# the others are to be moved
Move-Item -Path $file.FullName -Destination $tempLocation -Force
}
}

How to backup these files into specific folders using powershell

I've finally have given up googling and come here out of desperation. Go easy on me I'm fairly new to Powershell.
So, the objective of the code below was to first look through the source folder, then read through each .zip file and move to the directory specified by the value in the hashtable. Unfortunately, this is not how they want it to work anymore.
Now I need to retain the parent folder from source: for example "DAL" and then create the proceeding folders based on the file names and finally move each .zip to its file specified folder. Also, it needs to go through each folder under source which will be at least 20 other folders with a unique 3 character names.
$srcRoot = "C:\Cloud\source\dal"
$dstRoot = "C:\Cloud\Destination"
##$map = #{}; dir -recurse | ? { !$_.psiscontainer} | % { ##$map.add($_.name,$_.PSChildName) }
# DAT and DEV will have to be excluded from folder creation
$map = {
#AEODDAT_201901 = "AEOD\2019\01"
#AEOMDEV_201902 = "AEOM\2019\01"
#AEOYDAT_201902 = "AEOY\2019\01"
}
$fileList = Get-ChildItem -Path $srcRoot -Filter "*.zip*" -File -Force -Recurse
foreach ($file in $fileList)
{
#Go through each file up to mapped string
$key = $file.BaseName.Substring(0,14)
if ($key -in $map.Keys)
{
$fileName = $file.Name
$dstDir = Join-Path -Path $dstRoot -ChildPath $map[$key]
#create direcotory if not in path
if (-not (Test-Path -Path $dstDir))
{
mkdir -Path $dstDir
}
Write-Verbose "Moving $($file.FullName)"
if (Test-Path -Path (Join-Path -Path $dstDir -ChildPath $fileName))
{
#Write error if name exists
Write-Error -Message "File $fileName already exists at $dstDir"
#move path
} else {
Move-Item -Path $($file.FullName) -Destination $dstDir
}
}
}
So C:\Cloud\source\DAL\AEODDAT20190101.zip should create folders in C:\Cloud\Destination\DAL\AEOD\2019\01\AEODDAT20190101.zip would be my desired output.
Welcome, Matt! (no pun intended) One of the habits I have in similar situations with destination folders is to Set-Location $dstRoot and create folders from the relative path. You can execute New-Item with the relative path and the syntax is simpler. For example, your If statement could look like this and it would work the same way (with a slightly different error message):
if ($key -in $map.Keys){
Set-Location $dstRoot
New-Item -ItemType Directory $map[$key] -ErrorAction Ignore #won't raise an error if it exists
Write-Verbose "Moving $($file.FullName)"
#this will raise an error if the file already exists, unless you specify -Force
Move-Item "$($file.FullName)" $map[$key]
}
EDIT: Found 2 issues.
$map is a Hashtable literal that should be preceded with #:
$map = #{
AEODDAT20190101 = "AEOD\2019\01"
You were missing the last character of the base file name by taking only the first 14 characters. AEODDAT2019010 didn't match AEODDAT20190101. This should fix it:
$key = $file.BaseName.Substring(0,15)

Powershell copy and rename files

I'm trying to copy files from a source folder to a destination folder, and rename the files in the process.
$Source = "C:\Source"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
$Destination = "\\Server01\Destination"
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -
Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
The problem is that since Get-ChildItem doesn't throw an error message if the file is not found, but rather just gives you a blank, I end up with a folder called File01.test in destination if no file named File* exists in $Source.
If it does exist, the copy operation carries out just fine. But I don't want a folder to be created if no matching files exist in $Source, rather I just want an error message logged in a log file, and no file operation to occur.
This shouldn't matter what the file name is, but it won't account for files that already exist in the destination. So if there is already File01.txt and you're trying to copy File01.txt again you'll have problems.
param
(
$Source = "C:\Source",
$Destination = "\\Server01\Destination",
$Filter = "File*"
)
$Files = `
Get-ChildItem -Path $Source `
| Where-Object -Property Name -Like -Value $Filter
for ($i=0;$i -lt $Files.Count;$i++ )
{
$NewName = '{0}{1:D2}{3}' -f $Files[$i].BaseName,$i,$Files[$i].Extension
$NewPath = Join-Path -Path $Destination -ChildPath $NewName
try
{
Write-Host "Moving file from '$($Files[$i].FullName)' to '$NewPath'"
Copy-Item -Path $Files[$i] -Destination
}
catch
{
throw "Error moving file from '$($Files[$i].FullName)' to '$NewPath'"
}
}
You can add an "if" statement to ensure that the code to copy the files only runs when the file exists.
$Source = "C:\Source"
$Destination = "\\Server01\Destination"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
if ($File01) {
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
} else {
Write-Output "File did not exist in $source" | Out-File log.log
}
In the "if" block, it will check to see if $File01 has anything in it, and if so, then it'll run the subsequent code. In the "else" block, if the previous code did not run, it'll send the output to the log file "log.log".

Copy items from Source to Destination if they don't already exist

I have a pretty basic powershell copy script that copies items from a source folder to a destination folder. However this is moving way too much data, and I'd like to check if the filename already exists so that file can be ignored. I don't need this as complex as verifying created date/checksum/etc.
Currently it's along the lines of:
Copy-Item source destination -recurse
Copy-Item source2 destination2 -recurse
I'd imagine I need to add the Test-Path cmdlet, but I'm uncertain how to implement it.
You could always call ROBOCOPY from PowerShell for this.
Use the /xc (exclude changed) /xn (exclude newer) and /xo (exclude older) flags:
robocopy /xc /xn /xo source destination
This will ONLY copy those files that are not in the destination folder.
For more option type robocopy /?
$exclude = Get-ChildItem -recurse $dest
Copy-Item -Recurse $file $dest -Verbose -Exclude $exclude
While I agree that Robocopy is the best tool for something like this, I'm all for giving the customer what they asked for and it was an interesting PowerShell exercise.
This script should do just what you asked for: copy a file from Source to Destination only if it does not already exist in the Destination with a minimum of frills. Since you had the -recurse option in your example, that made for a bit more coding than just simply testing for the filename in the Destination folder.
$Source = "C:\SourceFolder"
$Destination = "C:\DestinationFolder"
Get-ChildItem $Source -Recurse | ForEach {
$ModifiedDestination = $($_.FullName).Replace("$Source","$Destination")
If ((Test-Path $ModifiedDestination) -eq $False) {
Copy-Item $_.FullName $ModifiedDestination
}
}
Building off of Wai Ha Lee's post, here's an example that worked for me:
$Source = "<your path here>"
$Dest = "<your path here>"
$Exclude = Get-ChildItem -recurse $Dest
Get-ChildItem $Source -Recurse -Filter "*.pdf" | Copy-Item -Destination $Dest -Verbose -Exclude $Exclude
This builds a list to exclude, then copies any pdf in the source directory and sub-directories to the destination in a single folder...excluding the existing files. Again, this is an example from my needs, but similar to yours. Should be easy enough to tweak to your hearts content.
Function Copy-IfNotPresent will accept one file at a time but it's easy to loop for all files you want to copy. Here's an example:
gci c:\temp\1\*.* -Recurse -File | % { Copy-IfNotPresent -FilePath $_ -Destination "C:\temp\2\$(Resolve-Path $_ -relative)" -Verbose }
Here's the function. It will generate the folder tree if necessary. Here's the gists link: https://gist.github.com/pollusb/cd47b4afeda8edbf8943a8808c880eb8
Function Copy-IfNotPresent {
<#
Copy file only if not present at destination.
This is a one file at a time call. It's not meant to replace complex call like ROBOCOPY.
Destination can be a file or folder. If it's a folder, you can use -Container to force Folder creation when not exists
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
$FilePath,
[Parameter(Mandatory)]
[string]$Destination,
[switch]$Container,
[switch]$WhatIf
)
#region validations
if ($FilePath -isnot [System.IO.FileInfo]){
$File = Get-ChildItem $FilePath -File
} else {
$File = $FilePath
}
if (!$File.Count){
Write-Warning "$FilePath no file found."
return
} elseif ($File.Count -gt 1) {
Write-Warning "$FilePath must resolve to one file only."
return
}
#endregion
# Destination is a folder
if ($Container -or (Test-Path -Path $Destination -PathType Container)) {
if (!(Test-Path $Destination)) {
New-Item -Path $Destination -ItemType Container | Out-Null
}
$Destination += "\$($File.Name)"
}
# Destination is a file
if (!(Test-Path $Destination)) {
if ($WhatIf) {
Write-Host "WhatIf:Copy-IfNotPresent $FilePath -> $Destination"
} else {
# Force creation of parent folder
$Parent = Split-Path $Destination -Parent
if (!(Test-Path $Parent)) {
New-Item $Parent -ItemType Container | Out-Null
}
Copy-Item -Path $FilePath -Destination $Destination
Write-Verbose "Copy-IfNotPresent $FilePath -> $Destination (is absent) copying"
}
} else {
Write-Verbose "Copy-IfNotPresent $Destination (is present) not copying"
}
}
$source = "c:\source"
$destination = "c:\destination"
Create a list of files to exclude, i.e. files already existing in the destination.
$exclude = Get-Childitem -Recurse $destination | ForEach-Object { $_.FullName -replace [Regex]::Escape($destination ), "" }
Recursively copy all contents from the source to the destination excluding the previously collected files.
Copy-Item -Recurse -Path (Join-Path $source "*") -Destination $destination -Exclude $exclude -Force -Verbose
(Join-Path $source "*") add a wildcard at end ensuring that you get the children of the source folder instead of the source folder itself.
Force is used because I don't mind that there are already existing folders (results in error messages). Use with caution.
ForEach-Object { $_.FullName -replace [Regex]::Escape($destination ), "" } transforms the existing file full names into values which can be used as Exclude parameter
Here is a recursive script that syncronizes 2 folders ignoring existing files:
function Copy-FilesAndFolders([string]$folderFrom, [string]$folderTo) {
$itensFrom = Get-ChildItem $folderFrom
foreach ($i in $itensFrom)
{
if ($i.PSIsContainer)
{
$subFolderFrom = $folderFrom + "\" + $i.BaseName
$subFolderTo = $folderTo + "\" + $i.BaseName
Copy-FilesAndFolders $subFolderFrom $subFolderTo | Out-Null
}
else
{
$from = $folderFrom + "\" + $i.Name
$to = $folderTo + "\" + $i.Name
if (!(Test-Path $from)) # only copies non-existing files
{
if (!(Test-Path $folderTo)) # if folder doesn't exist, creates it
{
New-Item -ItemType "directory" -Path $folderTo
}
Copy-Item $from $folderTo
}
}
}
}
To call it:
Copy-FilesAndFolders "C:\FromFolder" "C:\ToFolder"
Lots of great answers in here, here's my contribution as it relates to keeping an mp3 player in sync with a music library.
#Tom Hubbard, 10-19-2021
#Copy only new music to mp3 player, saves time by only copying items that don't exist on the destination.
#Leaving the hardcoded directories and paths in here, sometimes too much variable substitution is confusing for newer PS users.
#Gets all of the albums in the source directory such as your music library
$albumsInLibrary = gci -Directory -path "C:\users\tom\OneDrive\Music" | select -ExpandProperty Name
#Gets all of the albums of your destination folder, such as your mp3 player
$albumsOnPlayer = gci -Directory -Path "e:\" | select -ExpandProperty name
#For illustration, it will list the differences between the music library and the music player.
Compare-Object -DifferenceObject $albumsInLibrary -ReferenceObject $albumsOnPlayer
#Loop through each album in the library
foreach ($album in $albumsInLibrary)
{
#Check to see if the music player contains this directory from the music library
if ($albumsOnPlayer -notcontains $album)
{
#If the album doesn't exist on the music player, copy it and it's child items from the library to the player
write-host "$album is not on music player, copying to music player" -ForegroundColor Cyan
Copy-Item -path "C:\users\Tom\OneDrive\music\$album" -Recurse -Destination e:\$album
}
}