Just a small issue and i'm likely missing something very simple
I've setup a script just to make a few folders as part of a bigger script
I've tried to set it up so that when the script is ran a new directory is created with the date in as so:
$Date = ((Get-Date).ToShortDateString())
$CompileFolder = "C:\Registry_Export\PostInfection\$Date\Compiled Postinfection"
New-Item -path $CompileFolder -ItemType Directory
The script works as intended (The part above is just a piece from the script)
However when the directories are generated they are creating sub directories instead of just one directory.
For Example 23/03/2017 should make a folder called 23/03/2017
However it is making a folder structure of:
C:\Registry_Export\PostInfection\23\03\2017\Compiled Postinfection
I understand that this is because of the / that is generated because of the date however would like to know if there is an easy way to rectify this
Thank You
/ is an illegal file name character in NTFS (and, by extension, also an illegal directory name character).
I would suggest either using a different separator for your date string, like - or ., or just omitting a separator completely. To use a custom format, you can use either ToString(), the -f string format operator, or Get-Date -Format
# using hyphens (23-07-2017)
$Date = '{0:dd-MM-yyyy}' -f (Get-Date)
# using dots (23.07.2017)
$Date = (Get-Date).ToString('dd.MM.yyyy')
# using no separator (23072017)
$Date = Get-Date -Format ddMMyyyy
To complement Mathias R. Jessen's helpful answer:
Another way of putting it: any \ or / chars. in a name passed to New-Item -ItemType Directory are interpreted as path separators separating multiple directory names.
Generally, consider using a culture-invariant, sortable string format, such as the one provided by the s standard format string, which is based on the ISO 8601 standard:
> (Get-Date -Format s) -replace 'T.*'
2017-03-23
The -replace 'T.*' part simply cuts off the time portion.
Related
I need to read a file (e.g. file.txt) which has file names as its content. File names are separated by unique character (e.g. '#'). So my file.txt looks something like:
ABC.txt#
CDE.csv#
XYZ.txt#
I need to read its content line by line based on its extension. I have 1 source folder and 1 destination folder. Below is my scenario that I need to achieve:
If extension = txt then
check if that file name exists in destination_folder1 or destination_folder2
if that file exists then
copy that file from source_folder1 to destination_folder1
else delete that file from destination_folder1
Else display msg as "Invalid file"
I am new to powershell scripting. can someone pls help? Thanks in advance.
It will make my job easier if we assume the following pseudocode. Then you can take the elements I demonstrate and change them to fit your needs.
If the string from "file.txt" contains the file extension "txt" then continue.
If the file does not exist in the destination folder then copy the file from the source folder to the destination folder.
Use Get-Content to read a text file.
Get-Content .\file.txt
Get-Content processes files line by line. This has a few consequences:
Each line in our input text file will trigger our code.
Each time our code triggers, it will have input that looks like this: ABC.txt#
We can focus on solving the problem for one line.
If we need to evaluate strings, I suggest using regular expressions.
Remember, we are operating on a single line from the text file:
ABC.txt#
We need to detect the file extension.
A good place to start would be the end of the string.
In regular expressions, the end of a string is represented by $
So let's start there.
Here is our regular expression so far:
$
The next thing that would be useful is if we accounted for that # symbol. We can do that by adding it before $
#$
If there was a different character, we would add that instead: ;$ Keep in mind that there are reserved characters in regular expressions. So we might need to escape certain characters with a backslash: \$$
Now we have to account for the file extension.
We have three letters, we don't know what they are.
Regular expressions have a special escape sequence (called a character class) that can match any letter: \w
Let's add three of those.
\w\w\w#$
Now, while crafting regular expressions, it is a good idea to limit the text we're looking for.
As humans, we know we're looking for .txt# But, so far, the computer only knows about txt# with no dot. So it would accept .txt#, .xlsx#, and anythingGoes# as matches. We limited the right side of our string. Now let's limit the left side.
We're only interested in three characters. And the left side is bounded by a . So let's add that to our regular expression. I'll also mention that a period is a reserved character in regular expressions. So, we will have to escape it.
\.\w\w\w#$
So if we're looking at text like this
ABC.txt#
then our regular expression will output text like this
.txt#
Now, .txt# is a pretty good result. But we can make our job a little easier by limiting the result to just the file extension.
There are several ways of doing this. But I suggest using regular expression groups.
We create a group by surrounding our target with parentheses.
\.(\w\w\w)#$
This now produces output like:
txt
From here, we can just make intuitive comparisons like if txt = txt.
Another piece of the puzzle is testing whether a file already exists. For this we can use the Test-Path and Join-Path cmdlets.
$destination = ".\destination 01"
$exampleFile = "ABC.txt"
$destinationFilePath = Join-Path -Path $destination -ChildPath $exampleFile
Test-Path -Path $destinationFilePath
With these concepts, it is possible to write a working example.
# Folder locations.
$source = ".\source"
$destination = ".\destination 01"
# Load input file.
Get-Content .\file.txt |
Where-Object {
# Enter our regular expression.
# I've added an extra group to capture the file name.
# The $matches automatic variable is created when the -match comparison operator is used.
if ($_ -match '([\w ]+\.(\w\w\w))#$')
{
# Which file extensions are we interested in processing?
# Here $matches[2] represents the file extension: ex "txt".
# We use a switch statement to handle each type of file extension.
# Accept new file types by creating new switch cases.
switch ($matches[2])
{
"txt" {$true; Break}
#"csv" {$true; Break}
#"pdf" {$true; Break}
default {$false}
}
}
else { $false }
} |
ForEach-Object {
# Here $matches[1] is the file name captured from the input file.
$sourceFilePath = Join-Path -Path $source -ChildPath $matches[1]
$destinationFilePath = Join-Path -Path $destination -ChildPath $matches[1]
$fileExists = Test-Path -Path $destinationFilePath
# Copy the source file to the destination if the destination doesn't exist.
if (!$fileExists)
{ Copy-Item -Path $sourceFilePath -Destination $destinationFilePath }
}
Note on Copy-Item
Copy-Item has known issues.
Issue #10458 | PowerShell | GitHub
Issue #2581 | PowerShell | GitHub
You can substitute robocopy which is more reliable.
Robocopy - Wikipedia
The robocopy syntax is:
robocopy <source> <destination> [<file>[ ...]] [<options>]
where <source> and <destination> can be folders only.
So, if you want to copy a file, you have to write it like this:
robocopy .\source ".\destination 01" ABC.txt
We can invoke robocopy using Start-Process and the variables we already have.
# Copy the source file to the destination if the destination doesn't exist.
if (!$fileExists)
{
Start-Process -FilePath "robocopy.exe" -ArgumentList "`"$source`" `"$destination`" `"$($matches[1])`" /unilog+:.\robolog.txt" -WorkingDirectory (Get-Location) -NoNewWindow
}
Using Get-ChildItem
You use file.txt as input. If you wanted to gather a list of files on disc, you can use Get-ChildItem.
Multiple Conditions
You wrote "destination_folder1 or destination_folder2". If you need multiple conditions you can construct this with three things.
Use the if statement. Inside the test condition, you can add multiple conditions with logical -or And you can group statements together to make them easier to read.
Functions
If you need to move a piece of code around, you can use a function. Just remember to create parameters for the inputs to the function. Then call a PowerShell function without parentheses or commas:
# Calling a PowerShell function.
myFunction parameterOne parameterTwo parameterThree
Writing Output
You can use Write-Output to send text to the console.
Write-Output "Invalid File"
Further Reading
Here are some references which you might find useful.
about_Comparison_Operators - PowerShell | Microsoft Docs
about_Pipelines - PowerShell | Microsoft Docs
about_Switch - PowerShell | Microsoft Docs
Regular-Expressions.info - Regex Tutorial, Examples and Reference - Regexp Patterns
Where-Object (Microsoft.PowerShell.Core) - PowerShell | Microsoft Docs
I wanted to use Powershell's Regex to match a specific string in a path. I then want to pipe that into Get-FileHash and get the MD5 hashes of all the files.
The path can change, depending on where the user has these files. So for instance, it can be
C:\Program Files\StackOverflow\Powershell\Regex
or
C:\StackOverflow\Powershell\Regex
I want to make it so that only the Regex portion is selected which I can then -Recurse and pipe into Get-FileHash. Also please note that there can be subfolders inside of Regex (for instance: /Regex/Folder1 and /Regex/Folder2)
I can't quite get how to go about it. Please help me with this, thank you.
A regex is not needed. There's Split-Path that parses paths and returns desired sections.
For example (this is on MacOS, should work the same on Windows)
PS >pwd
/Users/myHomeDir
PS >pwd | split-path -leaf
myHomeDir
Also, a string that contains a path can be processed. Like so,
$p = "C:\Program Files\StackOverflow\Powershell\Regex"
split-path -leaf $p
Regex
Try the following pattern using named capture group 'path'
(?i)^(?<path>.+?(?:\\|\/)Regex)(?:(?:\\|\/)+.*)?$
If you need case-senstive matches, just remove the (?i) at the very beginning.
The pattern is aware of / and \ as path separator (including end of path). The rest of the path is not captured (?:)
For full explanation and samples see: https://regex101.com/r/xxaJzY/1/
Then use it the following way:
$folderName = 'Regex'
$regexPattern = "(?i)^(?<path>.+?(?:\\|\/)$folderName)(?:(?:\\|\/)+.*)?$"
<some other code>
$path = [regex]::Match($item, $regexPattern).Groups['path'].Value
I have a script that takes the creation date of a file and injects it into an xml tag. Everything works but the formatting of the date shows 2019-4-3 and I need the format to be 2019-04-03.
I have tried the 'tostring(MM)' and that has not worked for me. Anyone have any ideas? Here is a snippet of the code where I think the issue is. I can post the whole script if anyone wants to see it.
$filename="C:\Users\Public\file.xml"
$filename2="C:\Users\Public\file2.xml"
$month = (Get-ChildItem $filename).CreationTime.Month
$day = (Get-ChildItem $filename).CreationTime.Day
$year = (Get-ChildItem $filename).CreationTime.Year
$hour = (Get-ChildItem $filename).CreationTime.Hour
$min = (Get-ChildItem $filename).CreationTime.Minute
$sec= (Get-ChildItem $filename).CreationTime.Second
$output=[string]$year+"-"+[string]$month+"-"+[string]$day+"T"+[string]$hour+":"+[string]$min+":"+[string]$sec+":00z"
Write-Host $output
The output of the dates are single digit and I need them double digits. Any ideas?
The following should work assuming you don't actually need each part of the datetime as a separate variable:
$filename="C:\Users\Public\file.xml"
(Get-ChildItem $filename).creationtime.toString('yyyy-MM-ddTHH:mm:ss')+':00z'
I don't know why the toString() method did not work for you previously unless you didn't quote contents inside the parentheses. The method doesn't always require an input argument, but with what you are trying it requires a string to be passed to it.
You are constructing this the hard way. Formatting dates and times as strings is fully explained here: https://ss64.com/ps/syntax-dateformats.html
A simpler way to do this, and faster since right now you are getting the file info 6 times right now, would be to use the built-in [datetime] method ToString().
(Get-Item $filename).CreationTimeUtc.ToString("yyyy-MM-ddTHH:mm:ss.00Z")
Notice that I used the CreationTimeUtc property instead of CreationTime, since the "Z" at the end of your string that you are creating indicates that this is the time in UTC (the "Z" stands for Zulu time, as explained in Wikipedia).
To complement TheMadTechnician's helpful answer:
Except for the T separating the date and time parts and a sub-second component, your format is the same as the standard u date/time format:
PS> (Get-Item /).CreationTimeUtc.ToString('u') -replace ' ', 'T'
2018-11-30T10:47:40Z
If you need an explicit zero-valued sub-second component, append -replace 'Z$', '.00Z' - note the use of ., not :, as the separator, because . is normally used.
I've been looking all over stackoverflow and other forums online for any help with PowerShell variables and the rename function. I've had no luck.
The filename convention I use is: CompanyName 12.31.15 FS
(The "FS" describes the content of the file, in this case financial statements). Single digit months are preceded by 3 spaces so that the "FS" always ends up in the same column visually in windows explorer.)
The date format is m.dd.yy though I would like to change it to yy.mm.dd. The problem is I have over 30 folders with myriad of files in them- Some don't follow this file naming convention but if they use the m.dd.yy format, then the filename certainly does follow the convention.
So here's what I'm looking for:
A way to switch around only the date part of the file name from m.dd.yy to yy.mm.dd
In so doing, to also remove 2 of the leading spaces (where there are currently 3) and only have one space between CompanyName, date, and content.
The files must be changed in several directories within the "companies" folder
Examples:
Currently:
CocaCola 12.31.15 FS
CocaCola 6.30.15 FS
I want:
CocaCola 15.12.31 FS
CocaCola 15.06.31 FS
Retrieve the files using the Get-ChildItem cmdlet. Rename it using Rename-Item.
To switch the date you could parse it and format it to your desired output. However you could also do everything using a regex:
$yourCompanyPath = 'C:\tmp'
$callback = {
param($match)
'{0} {1}.{2:D2}.{3} {4}' -f $match.Groups["CompanyName"].Value,
$match.Groups["Year"].Value,
[int]$match.Groups["Month"].Value,
$match.Groups["Day"].Value,
$match.Groups["FS"].Value
}
$rex = [regex]'(?<CompanyName>\S+)\s+(?<Month>\d+)\.(?<Day>\d+)\.(?<Year>\d+)\s+(?<FS>FS)'
Get-ChildItem $yourCompanyPath | Foreach {
$_ | Rename-Item -NewName ('{0}{1}' -f $rex.Replace($_.BaseName, $callback), $_.Extension)
}
Source example:
CocaCola 6.30.15 FS.csv
CocaCola 12.31.15 FS.csv
Output:
CocaCola 15.06.30 FS.csv
CocaCola 15.12.31 FS.csv
I have a stack load of images and videos on my Samsung phone. I copied these images to a USB then onto my PC.
I want to use Powershell to rename these files based on their Date Taken attribute.
Format required = yyyy-MM-dd HH.mm.ss ddd
I have been using a Powershell script (see below) that does this beautifully using the Date Modified attribute, but the copy above somehow changed the Date Modified value on me (WTH!), so I can't use that now (as its not accurate).
Get-ChildItem | Rename-Item -NewName {$_.LastWriteTime.ToString("yyyy-MM-dd HH.mm.ss ddd") + ($_.Extension)}
In summary - is there a way to change the file name based on the Date Taken file attribute? Suggestions I have seen online require use of the .NET System.Drawing.dll and convoluted code (I'm sure it works, but damn its ugly).
GG
Please checkout Set-PhotographNameAsDateTimeTaken Powershell module. It extract date and time from the picture and change name of the picture to it.
It allows to use -Recurse -Replace and -Verbose parameter. By default it will create reuslt folder at the same level as your working dir.
If you need change the format of the target names the code can be found here.
I 'glued' together a bunch of other answers to make a bulk script. Credit to those, but Chrome crashed and I lost those other webpages on Stack. This works on photo files only and will rename all files to YYYYMMDD_HHMMSS.jpg format.
Here it is:
$nocomment = [reflection.assembly]::LoadWithPartialName("System.Drawing")
get-childitem *.jpg | foreach {
$pic = New-Object System.Drawing.Bitmap($_.Name)
$bitearr = $pic.GetPropertyItem(36867).Value
$string = [System.Text.Encoding]::ASCII.GetString($bitearr)
$date = [datetime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
[string] $newfilename = get-date $date -format yyyyMd_HHmmss
$newfilename += ".jpg"
$pic.Dispose()
rename-item $_ $newfilename -Force
$newfilename
}
In order to avoid this error:
New-Object : Cannot find type [System.Drawing.Bitmap]: verify that the assembly containing this type is
loaded.
...
Make sure the required assembly is loaded before executing the code above:
add-type -AssemblyName System.Drawing