Powershell script if else statements - windows

-U returns the else echo statement and I can't figure out why. Everything else works if just seems to be ignoring my first if statement. The script functions for folder navigation. -U should return the /users/username directory. Thanks in advance for the help.
function display-path {Get-ChildItem Env:Path }
function folder {
[CmdletBinding(DefaultParameterSetName='Default')]
param(
[Alias('u')]
[Parameter(ParameterSetName='User')]
[switch] $Username
,
[Alias('s')]
[Parameter(ParameterSetName='Scripts')]
[switch] $Scripts
,
[Parameter(ParameterSetName='Desktop')]
[Alias('d')]
[switch] $Desktop
,
[Alias('h')]
[Parameter(ParameterSetName='help')]
[switch] $Help
,
[Alias('r')]
[Parameter(ParameterSetName='root')]
[switch] $root
)
$targetFolder =
if ($Username) {
$targetFolder += '\user\username'
}
if ($Scripts) {
$targetFolder += '\Desktop\Scripts'
}
elseif ($Desktop) {
$targetFolder += '\Desktop'
}
if ($root) {
$targetFolder += 'c:\'
}
else {
echo "
-H Display help. This is the same as not typing any options.
-U Change to the 'Username' directory
-S Change to the 'scripts' directory
-D Change to the 'desktop' directory"
}
Push-Location -LiteralPath $targetFolder
}
EDIT:
Here is the code updated with the else if statement that doesn't work, it is ignoring the first if statement.
function display-path {Get-ChildItem Env:Path }
<#
**One of these can be used to navigate to the username directory. The first will only allow you to navigate to one user, the second allows for you to select the user.**
$targetFolder =
if ($Username) {
$targetFolder += '\user\Username' #replace with your username
}
if ($Username) {
Join-Path (Split-Path -LiteralPath $HOME) $Username
} else {
$HOME
} #>
function folder {
[CmdletBinding(DefaultParameterSetName='Default')]
param(
[Alias('u')]
[switch] $Username
,
[Alias('s')]
[Parameter(ParameterSetName='Scripts')]
[switch] $Scripts
,
[Parameter(ParameterSetName='Desktop')]
[Alias('d')]
[switch] $Desktop
,
[Alias('h')]
[Parameter(ParameterSetName = 'help')]
[switch]$Help
,
[Alias('r')]
[Parameter(ParameterSetName = 'root')]
[switch]$root
)
$targetFolder =
if ($Username) {
$targetFolder += '\users\username
}
elseif ($Scripts) {
$targetFolder += '\Desktop\Scripts'
}
elseif ($Desktop) {
$targetFolder += '\Desktop'
}
elseif ($root) {
## same as other but we can use $env:homedrive for the root of C:
$targetFolder = $env:HOMEDRIVE + '\'
$r = ' -R '
}
elseif ($Help) {
echo "
-H Display help. This is the same as not typing any options.
-U Change to the 'Username' directory
-S Change to the 'scripts' directory
-D Change to the 'desktop' directory"
}
else {
echo "
-H Display help. This is the same as not typing any options.
-U Change to the 'Username' directory
-S Change to the 'scripts' directory
-D Change to the 'desktop' directory"
}
Push-Location -LiteralPath $targetFolder
}

If you want parameters to do something mutually exclusive and show help only if none are specified, you need to chain all your checks in a single if ... elseif ... elseif ... else chain:
if ($Username) {
$targetFolder += '\user\swmur'
}
elseif ($Scripts) {
$targetFolder += '\Desktop\Scripts'
}
elseif ($Desktop) {
$targetFolder += '\Desktop'
}
elseif ($root) {
$targetFolder += 'c:\'
}
else {
echo "
-H Display help. This is the same as not typing any options.
-U Change to the 'Username' directory
-S Change to the 'scripts' directory
-D Change to the 'desktop' directory"
}

I added some colorful commentary. This should be pretty close to what you're looking for.
function display-path {
<#
.SYNOPSIS
quick shortcut to get env:PATH
.DESCRIPTION
Call Get-ChildItem with the the -path set to "env:Path" which actually just outputs out the child items of the current folder...
.EXAMPLE
display-path | Format-List
Name : Path
Value : C:\Program Files\Eclipse Adoptium\jdk-17.0.2.8-hotspot\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPow
erShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\PuTTY\;C:\ProgramData\chocolatey\bin;C:\Program Files\Go\bin;C:\Program
Files\dotnet\;C:\Program Files\Eclipse Adoptium\jdk-17.0.2.8-hotspot\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\Syste
m32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\PuTTY\;C:\ProgramData\chocolatey\bin;C:\Program
Files\Go\bin;C:\Program Files\dotnet\;C:\Python310\Scripts;C:\Users\jedurham\AppData\Local\Programs\Microsoft VS Code\bin
.NOTES
not sure why anyone needs this
#>
Get-ChildItem -Path Env:Path
}
function folder {
<#
.SYNOPSIS
shortcut to move between folder someone uses often
.DESCRIPTION
shortcut to move between folder someone uses often.
can be used to quickly navigate to common directories.
.PARAMETER Username
Moves to the C:\Users\currentuser\ Folder.
.PARAMETER Scripts
Moves to a hard coded path called 'C:\Users\currentuser\Desktop\Scripts'
.PARAMETER Desktop
Moves to a hard coded path called 'C:\Users\currentuser\Desktop\'
.PARAMETER Help
Displays this file.
.PARAMETER root
Moves to the root of the current drive.
.EXAMPLE
folder -Username
C:> folder -U
You chose the -U flag!! Moving to C:\Users\currentuser\
.EXAMPLE
folder -Scripts
C:> folder -S
You chose the -S flag!! Moving to C:\Users\currentuser\Desktop\Scripts
.EXAMPLE
folder -Desktop
C:> folder -D
You chose the -D flag!! Moving to C:\Users\currentuser\Desktop\
.EXAMPLE
folder -root
C:\> folder -r
You chose the -R flag!! Moving to C:\
.NOTES
Needs a lot of work ....
v0.01
#>
[CmdletBinding(DefaultParameterSetName = 'Default')]
param(
[Alias('u')]
[Parameter(ParameterSetName = 'User')]
[switch]$Username
,
[Alias('s')]
[Parameter(ParameterSetName = 'Scripts')]
[switch]$Scripts
,
[Parameter(ParameterSetName = 'Desktop')]
[Alias('d')]
[switch]$Desktop
,
[Alias('h')]
[Parameter(ParameterSetName = 'help')]
[switch]$Help
,
[Alias('r')]
[Parameter(ParameterSetName = 'root')]
[switch]$root
)
$switchoutput = 'You chose the{0} flag!! Moving to {1}{2}'
if ($Username) {
## you need more comments in your code
## are you just trying to move the \user\current logged in?
## just use $env:USERPROFILE
$targetFolder = $env:USERPROFILE
$u = ' -U'
Write-Output -InputObject ($switchoutput -f $U, $targetFolder, '')
}
elseif ($Scripts) {
## a little tougher here because you need to hard code this path
## we could also ask for it ask an addendum to this switch :P
## ill do it this way
$targetFolder = $env:USERPROFILE
$s = ' -S '
## it might be better to define this else
$scriptspath = 'Desktop\Scripts'
$targetFolder = $env:USERPROFILE + $scriptspath
Write-Output -InputObject ($switchoutput -f $S, $targetFolder, '')
}
elseif ($Desktop) {
## same as above
## it might be better to define this else
$desktop = '\Desktop\'
$targetFolder = $env:USERPROFILE + $desktop
$d = ' -D '
Write-Output -InputObject ($switchoutput -f $d, $targetFolder, '')
}
elseif ($root) {
## same as other but we can use $env:homedrive for the root of C:
$targetFolder = $env:HOMEDRIVE + '\'
$r = ' -R '
Write-Output -InputObject ($switchoutput -f $r, $targetFolder, '')
}
else {
Write-Output -InputObject "
-H Display help. This is the same as not typing any options.
-U Change to the 'Username' directory
-S Change to the 'scripts' directory
-D Change to the 'desktop' directory"
-R Change to the Root of home directory"
}
if (Test-Path -Path $targetFolder) {
Set-Location -LiteralPath $targetFolder -Verbose
}
else {
Write-Output -InputObject ('{0} was not found :( exiting' -f $targetFolder)
}
}

Related

in powershell, copy-item and test-path are both failing silently.. is it my code, or something else...?

I frequently have to copy a single file to multiple destinations, so i'm trying to write a script to make that go faster. it seems to work fine when i'm dealing with local files, but fails without any errors when running on a file that is on a mapped network drive.
at first I was using copy-item, and I couldn't make that work, so i used robocopy. that does the trick, but if the file already exists, i have an if statement using test-path which is supposed to skip to a user input that asks if you want to overwrite.. this is not working. i should say the one that checks the folder exists is working, but the one that checks for the file name always comes back true. for now, i have it just forcing an overwrite with robocopy because most of the time that's what i'll want to do anyway.
here's what i have right now.. "K:" is the mapped network drive i'm copying to, and i'm usually copying files from another mapped network drive "T:". I also should mention i have this set up to run from the context menu in windows (7) explorer, and it passes the file path to the script via %L and $args.
any advice is appreciated. (i apologize in advance, i know it's rather rough.. This is somewhat new to me.)
$Folders = #("K:\OKKHM 800" , "K:\OKKHM 1000" , "K:\OKKHM 1002" , "K:\OKKHM 1003" , "K:\OKKHM 1004", "K:\OKKHM 1250")
$source = $args[0]
$Filename = Split-Path -Path $source -Leaf
$sourcefolder= split-path -path $source -parent
$COUNTER = 0
$successful=0
$CONFIRMATION=0
foreach($Folder in $Folders){
$newpath = $folder + "\" + $filename
WRITE-HOST $NEWPATH
if(-not(test-path -path $newpath)) {
if((test-path -path $folder)) {
WRITE-HOST 'TEST 2'
robocopy $sourcefolder $folder $filename -is -it
$successful=1
}
else{
write-host 'folder does not exist'
}
}
else {
$title = 'Existing File Will Be Overwritten'
$question = 'Are you sure you want to proceed?'
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'confirmed'
$CONFIRMATION=1
}
else {
Write-Host 'cancelled'
$CONFIRMATION=0
}
IF ($CONFIRMATION -EQ 1) {
try {
robocopy $sourcefolder $folder $filename
$successful=1
}
catch {
throw "NO GOOD"
}
}
}
$COUNTER++
}
if ($successful -eq 1) {
WRITE-HOST 'SUMMARY: ' $COUNTER ' FILES COPIED SUCCESSFULLY.'
}
Start-Sleep 5

Powershell issues with parameters: 'Cannot convert value "" to type "System.Boolean"'

I wrote a PowerShell utility that takes in a couple parameters, and transfers files from a source directory to a destination directory.
Initially, all was done as a single function, and worked well enough.
Before adding some features, I broke repeated logic into its own function.
Then, the ISSUES began.
It appears that the Param() variables are seeded with incorrect values. Running the script yields the following:
PS ...> .\photoTransfer.ps1 E:\DCIM\100OLYMP
Cannot convert value "" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as
$True, $False, 1 or 0.
At C:\Users\SWPhantom\Desktop\admin\photoTransfer.ps1:85 char:3
+ [Parameter(Mandatory=$true, Position = 0, HelpMessage = "The path o ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
I can confirm that something's strange with
Write-Output "src: $source", which spits out src: True. (Expected to be src: E:\DCIM\100OLYMP)
HOWEVER: I can get the value I expect to be passed in with an $args[0].
I expect that the issue is simple, but I can't pick up on it, as this was my first foray into more... mature PowerShell scripting.
I am getting around the immediate problem by using the $args[i] method, but it'd be nice to not get an error message and use the seemingly Nice and orderly Params. (Especially since they seemed to work before I made the separate Transfer function).
Thanks!
Full code:
# Purpose: Transfer all photos from a memory card, to a destination folder, organized by year, month, date.
# Ensure that the Date Modified and Date Created is preserved.
function Transfer {
Param(
[Parameter(Mandatory, Position = 0)]
[string]$src,
[Parameter(Mandatory, Position = 1)]
[string]$dst,
[Parameter(Mandatory, Position = 2)]
[string]$extension
)
# Look at the source directory. Enumerate files to be sent over. (Only copy .ORF/.MOV files)
$files = Get-ChildItem -Path $src -Include $extension -Recurse
$numberOfFiles = $files.Count
if($numberOfFiles -eq 0) {
return "No $extension files found in $src!"
}
# Give user last chance to stop program. Show them number of files and destination folder.
Write-Output "Ensure the action is correct:"
read-host "Copying $numberOfFiles files from $src to $dst ?`nPress Enter to continue"
# Iteration for progress tracking.
$iter = 1
# Foreach file, check the Date Modified field. Make sure the destination folder has the folder structure like:
# Drive/Photos/YYYY/MM/DD/
# Where the YMD matches the Date Modified field of every photo.
foreach ($file in $files) {
$originalCreationTime = $file.LastWriteTime
[string]$year = $file.LastWriteTime.Year
[string]$month = $file.LastWriteTime.Month
[string]$date = $file.LastWriteTime.Day
# Add leading zero, if necessary
if($month.length -lt 2) {
$month = "0" + $month
}
if($date.length -lt 2) {
$date = "0" + $date
}
# Test the path of destinationPath/YYYY/MM/DD/
$path = $dst + "$year\$month\$date\"
if (!(Test-Path -Path $path)) {
if($verb) {
Write-Output " $path"
}
New-Item -ItemType Directory -Path $path
}
# The filepath exists!
if($verb) {
Write-Output " ($iter/$numberOfFiles) $file -> $path"
}
$iter += 1
Copy-Item $file.FullName -Destination $path
# Fix the Creation Time
$(Get-Item -Path "$path$($file.Name)").CreationTime=$originalCreationTime
}
Write-Output "`nCopying done!`n"
# Delete items?
Write-Output "Delete $numberOfItems items?"
$del = read-host "Deleting copied files from $src ?`nY to continue"
if($del -eq "Y") {
foreach ($file in $files) {
Remove-Item $file.FullName
}
}
}
Param(
# Source Folder
[Parameter(Mandatory=$true, Position = 0, HelpMessage = "The path of the source of the media")]
[Alias("s")]
[string]$source,
# Photo Destination
[Parameter(Mandatory=$false, Position = 1, HelpMessage = "The path of the folder you want to move photos to")]
[Alias("pd")]
[string]$photoDestination,
# Video Destination
[Parameter(Mandatory=$false, Position = 2, HelpMessage = "The path of the folder you want to move videos to")]
[Alias("vd")]
[string]$videoDestionation,
# Verbosity
[Parameter(Position = 3, HelpMessage = "Turn extra logging on or off")]
[Alias("v")]
[bool]$verb = $true
)
$usageHelpText = "usage:`n photoTransfer.ps1 <DriveName> <pathToDestinationRootFolder>`nex:`n .\photoTransfer.ps1 C T:\Photos"
#TODO: Solve this conundrum, where passing a via CMD
# Write-Output "Source before treatment: $($args[0])"
# Write-Output "Source before treatment: $($args[1])"
# Write-Output "Source before treatment: $($args[2])"
# Write-Output "Source before treatment: $($args[3])"
$source = $args[0]
$verb = $true
# I expect a drive name. If a ':' is missing, I add it.
if(!$source.Contains(":")) {
$source = $source + ":"
}
# The assumption is that the photos are coming from my Olympus camera, which has the following path to the files.
# $olympusFolderPath = "DCIM\100OLYMP\"
# $source += $olympusFolderPath
# Make sure the destination path has a terminating '\'
# if(!($photoDestination -match "\\$")) {
# $photoDestination = $photoDestination + "\"
# }
$photoDestination = "T:\Photos\"
$videoDestionation = "T:\Footage\"
# Check if the source and destination paths are valid.
if (!(Test-Path -Path $source)) {
Write-Output "Source disk ($source) doesn't exist`n$usageHelpText"
exit 0
}
if (!(Test-Path -Path $photoDestination)) {
Write-Output "Destination path ($photoDestination) doesn't exist`n$usageHelpText"
exit 0
}
if (!(Test-Path -Path $videoDestionation)) {
Write-Output "Destination path ($videoDestionation) doesn't exist`n$usageHelpText"
exit 0
}
Transfer $source $photoDestination "*.ORF"
Transfer $source $videoDestionation "*.MOV"

Move contents of folder (Download) into recycle.bin folder - Powershell [duplicate]

When using the rm command to delete files in Powershell, they are permanently deleted.
Instead of this, I would like to have the deleted item go to the recycle bin, like what happens when files are deleted through the UI.
How can you do this in PowerShell?
2017 answer: use the Recycle module
Install-Module -Name Recycle
Then run:
Remove-ItemSafely file
I like to make an alias called trash for this.
If you don't want to always see the confirmation prompt, use the following:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('d:\foo.txt','OnlyErrorDialogs','SendToRecycleBin')
(solution courtesy of Shay Levy)
It works in PowerShell pretty much the same way as Chris Ballance's solution in JScript:
$shell = new-object -comobject "Shell.Application"
$folder = $shell.Namespace("<path to file>")
$item = $folder.ParseName("<name of file>")
$item.InvokeVerb("delete")
Here is a shorter version that reduces a bit of work
$path = "<path to file>"
$shell = new-object -comobject "Shell.Application"
$item = $shell.Namespace(0).ParseName("$path")
$item.InvokeVerb("delete")
Here's an improved function that supports directories as well as files as input:
Add-Type -AssemblyName Microsoft.VisualBasic
function Remove-Item-ToRecycleBin($Path) {
$item = Get-Item -Path $Path -ErrorAction SilentlyContinue
if ($item -eq $null)
{
Write-Error("'{0}' not found" -f $Path)
}
else
{
$fullpath=$item.FullName
Write-Verbose ("Moving '{0}' to the Recycle Bin" -f $fullpath)
if (Test-Path -Path $fullpath -PathType Container)
{
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else
{
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
}
}
Remove file to RecycleBin:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('e:\test\test.txt','OnlyErrorDialogs','SendToRecycleBin')
Remove folder to RecycleBin:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::Deletedirectory('e:\test\testfolder','OnlyErrorDialogs','SendToRecycleBin')
Here's slight mod to sba923s' great answer.
I've changed a few things like the parameter passing and added a -WhatIf to test the deletion for the file or directory.
function Remove-ItemToRecycleBin {
Param
(
[Parameter(Mandatory = $true, HelpMessage = 'Directory path of file path for deletion.')]
[String]$LiteralPath,
[Parameter(Mandatory = $false, HelpMessage = 'Switch for allowing the user to test the deletion first.')]
[Switch]$WhatIf
)
Add-Type -AssemblyName Microsoft.VisualBasic
$item = Get-Item -LiteralPath $LiteralPath -ErrorAction SilentlyContinue
if ($item -eq $null) {
Write-Error("'{0}' not found" -f $LiteralPath)
}
else {
$fullpath = $item.FullName
if (Test-Path -LiteralPath $fullpath -PathType Container) {
if (!$WhatIf) {
Write-Verbose ("Moving '{0}' folder to the Recycle Bin" -f $fullpath)
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else {
Write-Host "Testing deletion of folder: $fullpath"
}
}
else {
if (!$WhatIf) {
Write-Verbose ("Moving '{0}' file to the Recycle Bin" -f $fullpath)
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else {
Write-Host "Testing deletion of file: $fullpath"
}
}
}
}
$tempFile = [Environment]::GetFolderPath("Desktop") + "\deletion test.txt"
"stuff" | Out-File -FilePath $tempFile
$fileToDelete = $tempFile
Start-Sleep -Seconds 2 # Just here for you to see the file getting created before deletion.
# Tests the deletion of the folder or directory.
Remove-ItemToRecycleBin -WhatIf -LiteralPath $fileToDelete
# PS> Testing deletion of file: C:\Users\username\Desktop\deletion test.txt
# Actually deletes the file or directory.
# Remove-ItemToRecycleBin -LiteralPath $fileToDelete
Here is a complete solution that can be added to your user profile to make 'rm' send files to the Recycle Bin. In my limited testing, it handles relative paths better than the previous solutions.
Add-Type -AssemblyName Microsoft.VisualBasic
function Remove-Item-toRecycle($item) {
Get-Item -Path $item | %{ $fullpath = $_.FullName}
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
Set-Alias rm Remove-Item-toRecycle -Option AllScope

robocopy adds hidden symbol when creating folders

What I do, is copying photo files from SD card to HDD using powershell ps1 file and Windows PowerShell ISE.
I get a taken date from image exif and add it to destination path.
The problem is that robocopy creates folders and adds strange prefix, which I do not want to have.
As a result I can see two subfolders with same name "2020", one folder created by hand and the other created by robocopy.
This prefix is only seen when I list folders with CMD.
The prefix not seen in output.log and in powershell.
$copy_from = "G:\DCIM\100MSDCF\"
$copy_to = "C:\Photos\"
function GetDateTaken {
param (
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName')]
[String]
$Path
)
begin {
$shell = New-Object -COMObject Shell.Application
}
process {
$returnvalue = 1 | Select-Object -Property Name, DateTaken, Folder
$returnvalue.Name = Split-Path $path -Leaf
$returnvalue.Folder = Split-Path $path
$shellfolder = $shell.Namespace($returnvalue.Folder)
$shellfile = $shellfolder.ParseName($returnvalue.Name)
$returnvalue.DateTaken = $shellfolder.GetDetailsOf($shellfile, 12)
$returnvalue.DateTaken
}
}
$file = Get-ChildItem -Path $copy_from -recurse -include ('*.jpg','*.arw')
$i = 0
$jpg = 0
$arw = 0
$logifile = 'output.log'
if ([System.IO.File]::Exists($logifile)) {
Clear-Content $logifile
Write-Host ("Logfile cleaned: $logifile")
} else {
try {
New-Item -Path . -Name $logifile | Out-Null
Write-Host ("New logfile created: $logifile")
}
catch {
"Failed to create $logifile"
}
}
foreach ($file in $file) {
if ($file.extension -eq '.JPG') { $jpg++ }
if ($file.extension -eq '.ARW') { $arw++ }
$i++
$datetaken = ($file.fullname | GetDateTaken).Split(' ')[0]
$datetaken_Day = $datetaken.Split('.')[0]
$datetaken_Month = $datetaken.Split('.')[1]
$datetaken_Year = $datetaken.Split('.')[2]
$TargetPath = "$copy_to$datetaken_Year\$datetaken_Month\$datetaken_Day\"
Write-Host ("$i. " + $file.Name + " `tDate taken: " + $datetaken)
robocopy $copy_from $TargetPath $file.Name /ts /fp /v /np /unilog+:$logifile | Out-Null
}
Write-Host ("`nTotal: " + $i + " files (" + $jpg + " JPG files, " + $arw + " ARW files)")
Not helps if write $TargetPath = $copy_to + $datetaken_Year + "\" + $datetaken_Month + "\" + $datetaken_Day + "\".
Not helps if I set /fat option to robocopy.
But, for example, when I set a year manualy, everything is ok $datetaken_Year = 2020
What should be fixed to create correct folder names?
Using the GetDetailsOf() method from the COM object returns localized results, which leads to your function on my Dutch machine returning the date in 'dd-MM-yyyy HH:mm' format (with invisible characters surrounding it).
A better approach IMO would be to get the date taken using System.Drawing.Imaging.Metafile to read the exif data as null-terminated byte array and parse the date from that as DateTime object using below function:
function Get-ExifDate {
# returns the 'DateTimeOriginal' property from the Exif metadata in an image file if possible
[CmdletBinding(DefaultParameterSetName = 'ByName')]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByName')]
[Alias('FullName', 'FileName')]
[ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
[string]$Path,
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'ByObject')]
[System.IO.FileInfo]$FileObject
)
Begin {
Add-Type -AssemblyName 'System.Drawing'
}
Process {
# the function received a path, not a file object
if ($PSCmdlet.ParameterSetName -eq 'ByName') {
$FileObject = Get-Item -Path $Path -Force -ErrorAction SilentlyContinue
}
# Parameters for FileStream: Open/Read/SequentialScan
$streamArgs = #(
$FileObject.FullName
[System.IO.FileMode]::Open
[System.IO.FileAccess]::Read
[System.IO.FileShare]::Read
1024, # Buffer size
[System.IO.FileOptions]::SequentialScan
)
try {
$stream = New-Object System.IO.FileStream -ArgumentList $streamArgs
$metaData = [System.Drawing.Imaging.Metafile]::FromStream($stream)
# get the 'DateTimeOriginal' property (ID = 36867) from the metadata
# Tag Dec TagId Hex TagName Writable Group Notes
# ------- --------- ------- -------- ----- -----
# 36867 0x9003 DateTimeOriginal string ExifIFD (date/time when original image was taken)
# get the date taken as an array of bytes
$exifDateBytes = $metaData.GetPropertyItem(36867).Value
# transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
$exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
# return the parsed date
return [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null)
}
catch{
Write-Warning -Message "Could not read Exif data from '$($FileObject.FullName)'"
}
finally {
If ($metaData) {$metaData.Dispose()}
If ($stream) {$stream.Close()}
}
}
}
Another option would be to download and unzip ExifTool
(you can download the zip files from here)
Then use it like:
$exifTool = 'Path\To\Unzipped\ExifTool.exe' # don't forget to 'Unblock' after downloading
$file = 'Path\To\The\ImageFile' # fullname
# retrieve all date tags in the file
# -s2 (or -s -s) return short tag name add the colon directly after that
$allDates = & $exifTool -time:all -s2 $file
# try to find a line with tag 'DateTimeOriginal', 'CreateDate' or 'ModifyDate'
# which will show a date format of 'yyyy:MM:dd HH:mm:ss'
# and parse a DateTime object out of this string
$dateTaken = switch -Regex ($allDates) {
'^(?:DateTimeOriginal|CreateDate|ModifyDate):\s(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})' {
[datetime]::ParseExact($matches[1], 'yyyy:MM:dd HH:mm:ss', $null)
break
}
}
Short explanation of what the above returns
Both methods return the date the image was taken as a DateTime object, not a string.
This object has properties like .Year, .Month, .Day etc. It also has various methods like .AddDays(), .ToShortDateString(), .ToString() and a lot more.
If you do $datetaken = ($datetaken -split ' ')[0] as per your comment, you are asking PowerShell to implicitely convert it to a string using the default ToString() method.
You can use that ToString() method in your code if you give it the formatting string you need in between the brackets, anyway you like.
If you for instance do $dateTaken.ToString('yyyy\\MM\\dd'), you'll get a string 2020\10\08 if $dateTaken was today, which could serve as part of a file path.
In your code, you could do:
$TargetPath = Join-Path -Path $copy_to -ChildPath $dateTaken.ToString('yyyy\\MM\\dd')
# if that path does not exist yet, create it
if (!(Test-Path -Path $TargetPath -PathType Container)) {
$null = New-Item -Path $TargetPath -ItemType Directory
}
Then go ahead and copy the file to the now existing $TargetPath
Please have a look at all the standard format strings and custom format specifiers you can use on a DateTime object.

Is there any way to set a recursive environment variable in Windows?

I have an apps directory in my dropbox - I'd like to be able to access all of them from the command line without having to set up loads and loads of path variables. Is there any way to set up a recursive path variable? I tried putting ** at the end - no joy.
You can't use placeholders or anything like that in the PATH environment variable. It's just a concatenation of directories, no additional features.
So either add all of the app directories to the PATHenvironment variable or think about other ways to solve the problem. For example, you could add one directory to the PATH and place batch files named like the apps there that start the apps.
Made an account for this 11 year old question.
$path = Read-Host -Prompt "Enter the exact path that needs to be recursively added to the PATH env:"
$items = gci -Path $path -Recurse -Directory -name
$nuPath = $env:Path
$r = 0
write-Host "Env started as $nuPath"
foreach ($iitem in $items){
$addpath = ($path + "\" + $iitem)
$executabledir = $addpath + '\' + "*.exe"
if(test-path $executabledir){
Write-Host $addpath
$regexAddPath = [regex]::Escape($addPath)
$arrPath = $nuPath -split ';' | Where-Object {$_ -notMatch "^$regexAddPath\\?"}
$nuPath = ($arrPath + $addPath) -join ';'
++$r
}
}
$result = ($path + ";" + $nupath) -join ';'
$temp = New-TemporaryFile
$result.ToString() > $temp
Start-Process notepad.exe -ArgumentList $temp.FullName
$title = 'WARNING'
$question = "Your new environmental variable for PATH will be in the notepad window that popped up. are you sure you want to continue?"
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0 -and $r -gt 5) {
$title = 'Are you really sure?'
$question = 'This is larger than 5 entries and this can ruin your day if you mess it up. Just doublechecking everything is OK'
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
$env:Path > $HOME\pathbkup.txt
[Environment]::SetEnvironmentVariable("Path", $result, "Machine")
}
else {
Write-Host 'cancelled'
}
}
else {
Write-Host 'cancelled'
}
Remove-Item $temp

Resources