How to consume a click-event in a Toast-Message? - powershell

I made a powershell code that shows a toast-message with a "yes/no" option. Now I am looking for a way to handle that click-event properly. See below the code I have so far:
cls
Remove-Variable * -ea 0
$ErrorActionPreference = 'stop'
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
# now lets define a toast-message:
$toastXml = [Windows.Data.Xml.Dom.XmlDocument]::new()
$xmlString = #"
<toast launch = "Test1" scenario='alarm'>
<visual>
<binding template="ToastGeneric">
<text>Title</text>
<text>Message</text>
</binding>
</visual>
<audio src="ms-winsoundevent:Notification.Looping.Alarm" />
<actions>
<action content="yes" arguments="yes" />
<action content="no" arguments="no" />
</actions>
</toast>
"#
$toastXml.LoadXml($XmlString)
$toast = [Windows.UI.Notifications.ToastNotification]::new($toastXml)
$appId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId)
$notify.Show($toast)
Here I need a way to wait for the event, when the users clicks the "yes/no" buttons in the toast (or the "x" to close all). It should work without an external DLL, but using e.g. "add_Activated" and an eventHandler like in this c# code:
https://github.com/david-risney/PoshWinRT/blob/master/PoshWinRT/EventWrapper.cs
I thought it should be similar like for the other cs-Wrapper from that PoshWinRT project, which I could finally convert into this:
# replacement for PoshWinRT - AsyncOperationWrapper:
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = foreach($method in [System.WindowsRuntimeSystemExtensions].GetMethods()) {
if ($method.name -ne 'AsTask') {continue}
if ($method.GetParameters().Count -ne 1) {continue}
if ($method.GetParameters().ParameterType.Name -ne 'IAsyncOperation`1') {continue}
$method
break
}
function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, #($WinRtTask))
$null = $netTask.Wait(-1)
$netTask.Result
}
# sample for async operation:
$null = [Windows.Storage.StorageFile, Windows.Storage, ContentType = WindowsRuntime]
$file = 'c:\windows\notepad.exe'
$info = await ([Windows.Storage.StorageFile]::GetFileFromPathAsync($file)) ([Windows.Storage.StorageFile])
$info
Unfortunately, the coding for that eventWrapper seesm to be a bit more tricky. These are the code-snippets that I could not bring to life, but they may point into the right direction:
$method = [Windows.UI.Notifications.ToastNotification].GetMethod('add_Activated')
$handler = [Windows.Foundation.TypedEventHandler[Windows.UI.Notifications.ToastNotification,System.Object]]::new($toast, $intPtr1)
$handler = [System.EventHandler]::new($toast, $intPtr2)
Especially the second IntPtr-parameter required for creating an eventHandler confuses me totally.
Any input is more than welcome. Many thanks.

Related

Proccessing hashtable values correctly inside a ForEach-Object

I have a .xml file that I want to use to create windows .url files out of:
<?xml version="1.0" encoding="UTF-16" ?>
<items_list>
<item>
<title>About topics - PowerShell | Microsoft Learn</title>
<url>https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about?view=powershell-7.3</url>
</item>
<item>
<title>PowerShell HashTable - Everything you need to know — LazyAdmin</title>
<url>https://lazyadmin.nl/powershell/powershell-hashtable/</url>
</item>
<item>
<title>How a Regex Engine Works Internally</title>
<url>https://www.regular-expressions.info/engine.html</url>
</item>
</items_list>
I placed the Titles and URLs into a Hashtable:
$InXML = [XML](Get-Content .\test.xml)
$BookMarks = [ordered]#{
Title = (Select-Xml -xml $InXML -XPath "//title" | % {$_.Node.InnerXml}) -replace '\?|\\|/|:'
URL = Select-Xml -xml $InXML -XPath "//url" | % {$_.Node.InnerXml}
}
Everything good so far, then I start running into problems when I try to loop through the Titles and URLs:
$wshshell = New-Object -ComObject WScript.Shell
$BookMarks | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $shortcutFilePath
# $shortcutFilePath = Join-Path -path "c:\temp" -ChildPath "$Title.Url"
# $shortcut = $shell.CreateShortcut($shortcutFilePath)
# $shortcut.TargetPath = "$Url"
# $shortcut.Save()
}
I commented out my actuall code in the Loop to see whats actually happening with Write-Output. All the key values get concatenated into one long title or one long .url
What I am expecting is to get a single pair of a Url and Title at a time, so that I can create the .url file in the loop.
Reading the documentations and articles on how to do this. I tried variations of:
$BookMarks.Values | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $Title
Write-Output $Url
}
I keep getting null variables.
Any help or ideas would be really appreciated.
I suggest simplifying and streamlining your code as follows, which bypasses your problem:
[xml] $inXML = Get-Content -Raw .\test.xml
$inXml.items_list.item | ForEach-Object {
$title = $_.Title
$url = $_.Url
"[$title] [$url]" # sample output.
# ... work with $title and $url here
# $wshshell = New-Object -ComObject WScript.Shell
# ...
}
The above uses PowerShell's adaptation of the XML DOM, which allows you to access elements and attributes as if they were properties.

Don’t display Cancel in PowerShell script result

I have the following PowerShell script which displays file dialog to select a txt file. If user cancels dialog then provide a multiline text box
function GetDetails() {
Add-Type -AssemblyName System.Windows.Forms;
$browser = New-Object System.Windows.Forms.OpenFileDialog;
$browser.Filter = "txt (*.txt)|*.txt";
$browser.InitialDirectory = "E:\";
$browser.Title = "select txt file";
$browserResult = $browser.ShowDialog();
if($browserResult -eq [System.Windows.Forms.DialogResult]::OK) {
$nfoFile = $browser.FileName;
if([string]::IsNullOrWhiteSpace($txtFile)) {
return GetFromForm;
}
$txtFile = [System.IO.Path]::ChangeExtension($nfoFile, ".dac");
$txtFile = $temp + [System.IO.Path]::GetFileName($txtFile);
$exeArgs = "-f -S `"$txtFile`" -O `"$txtFile`"";
Start-Process $anExe -ArgumentList $exeArgs -Wait;
$result = Get-Content $txtFile | Out-String;
$browser.Dispose();
return $result;
} else {
return GetFromForm;
}
}
function GetFromForm(){
Add-Type -AssemblyName System.Windows.Forms;
$form = New-Object System.Windows.Forms.Form;
$form.Width = 800;
$form.Height = 600;
$txtBox = New-Object System.Windows.Forms.TextBox;
$txtBox.Multiline = $true;
$txtBox.AcceptsReturn = $true;
$txtBox.AcceptsTab = $true;
$txtBox.Visible = $true;
$txtBox.Name = "txtName";
$txtBox.Width = 760;
$txtBox.Height = 660;
$form.Controls.Add($txtBox);
$form.ShowDialog();
 
$form.Dispose();
return $txtBox.Text;
}
$desc = GetDetails;
cls;
Write-Host $desc;
Here I have two issues:
In Write-Host $desc, prints also Cancel hereiswhateverstrimg string if user chose to cancel dialog. How to avoid that?
If I run script in ISE, the generated form (in second function) will be always behind ISE even I call ShowDialog(), I expected to behave as modal dialog. It’s normal or there is a fix for this ?
Ok, there are a few changes that I made for efficiency and a few for functionality. Read the comments in the script for the explanations.
# Just add types once. There is no need to add the types in each function.
Add-Type -AssemblyName System.Windows.Forms;
Add-Type -AssemblyName System.Drawing
function GetDetails() {
$browser = New-Object System.Windows.Forms.OpenFileDialog;
$browser.Filter = "txt (*.txt)|*.txt";
$browser.InitialDirectory = "E:\";
$browser.Title = "select txt file";
$browserResult = $browser.ShowDialog();
# Combined the if statements
if($browserResult -eq [System.Windows.Forms.DialogResult]::OK -and [string]::IsNullOrWhiteSpace($txtFile) -ne $true) {
$nfoFile = $browser.FileName;
$txtFile = [System.IO.Path]::ChangeExtension($nfoFile, ".dac");
$txtFile = $temp + [System.IO.Path]::GetFileName($txtFile);
$exeArgs = "-f -S `"$txtFile`" -O `"$txtFile`"";
Start-Process $anExe -ArgumentList $exeArgs -Wait;
# The Raw flag should return a string
$result = Get-Content $txtFile -Raw;
$browser.Dispose();
return $result;
}
# No need for else since the if statement returns
return GetFromForm;
}
function GetFromForm(){
$form = New-Object System.Windows.Forms.Form;
$form.Text = 'Adding Arguments'
$form.Size = New-Object System.Drawing.Size(816,600)
$form.StartPosition = 'CenterScreen'
# Added a button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(585,523)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = 'OK'
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$txtBox = New-Object System.Windows.Forms.TextBox;
$txtBox.Multiline = $true;
$txtBox.AcceptsReturn = $true;
$txtBox.AcceptsTab = $true;
$txtBox.Visible = $true;
$txtBox.Name = "txtName";
$txtBox.Size = New-Object System.Drawing.Size(660,500)
$form.Controls.Add($txtBox);
# Needed to force it to show on top
$form.TopMost = $true
# Select the textbox and activate the form to make it show with focus
$form.Add_Shown({$txtBox.Select(), $form.Activate()})
# Finally show the form and assign the ShowDialog method to a variable (this keeps it from printing out Cancel)
$result = $form.ShowDialog();
# If the user hit the OK button return the text in the textbox
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
return $txtBox.Text
}
}
$desc = GetDetails;
cls;
Write-Host $desc;
You can see reference material here: https://learn.microsoft.com/en-us/powershell/scripting/samples/creating-a-custom-input-box?view=powershell-6
You need to suppress output of $form.ShowDialog() in GetFromForm:
$form.ShowDialog()|out-null
Powershell will add to a return value everything that was outputted to host within a function/commandlet.
Regarding your second issue - see this answer
And please do not use semi-colon at an end of line. This is not C# and will confuse you into thinking that the line is ended here but it's not quite true.

Copying slides from one Powerpoint Presantation to another with Powershell

I would like to copy a slide from one Powerpoint Presantation to another with Powershell.
Could anyone help me?
Here is my code which doesn't work:
Add-type -AssemblyName office
$npath = “C:\p3.pptx"
$test = Test-Path $npath
if($test = $true){Remove-Item $npath }
$ppo = New-Object -ComObject powerpoint.application
$p1 = “C:\p1.pptx"
$p2 = “C:\p2.pptx"
$pp1 = $ppo.Presentations.open($p1)
$pp2 = $ppo.Presentations.open($p2)
$pp2.Slides.item(54).copy()
$pp1.Slides.item(54).paste()
#I have tried it too, but it doesn't work:
# $cs = $pp2.Slides.item(54)
# $pp1.Slides.item(54).copy($cs)
$pp1.Saveas($npath)
$pp1.Close()
$pp2.Close()
$ppo.quit()
$ppo = $null
Try to use this method :
$pp2.Slides.InsertFromFile($p1,54,54,54)
Syntax :
FileName:=“C:\p1.pptx", Index:=54 (in the new ppt), SlideStart:=54, SlideEnd:=54
No need for copy and paste.

Powershell Checkedlistbox handling - checked item count inconsistency and missed click events

I'm having trouble with multiple issues with a checkedlistbox. Its content is the Windows feature name (commandline parameter to install a Windows feature via Powershell) and a description which really is its more readable name. Because I develop on Windows 7 and this command is only available on a Server platform I read the data from a XML file source. The XML file was created by the output of function BuildFeaturesFile, below. I've pasted a sample at the bottom if that is a problem.
1) I have to click an item twice to check it.
2) I have to double click an item slowly and surely to avoid the program missing the second click. Would I need to change the Windows control panel config to affect this or if I wanted (not too bothered but more curious) could I reduce the polling time to slicken the interface response?
3) This is my main issue. If I use the mouse to select an item it normally updates the count correctly however when I use the keyboard despite calling the same function I get different results; the first check is seemingly missed (doubled by the look of it reading further online to undo its action - however the event "only" fires once which causes misalignment of the displayed count to the true count. I think it maybe linked to the remark in the ItemCheck event documentation "The check state is not updated until after the ItemCheck event occurs."
http://msdn.microsoft.com/en-us/library/system.windows.forms.checkedlistbox.itemcheck(v=vs.110).aspx
The main code also updates a description text box but I removed that for brevity.
4) In function ftn_CheckAllItemsInCheckList the top remmed out line works for the first item but as I'm calling it by index seemingly the action of checking the item causes the index to change which unseats the action causing it to break.
Please can you assist - at least with questions #3 and #4 if possible?
Thanks in advance.
Shaun
function BuildForm
{
# Declare objects
$frm_BuildConfigurator = New-Object System.Windows.Forms.Form
$btn_Cancel = New-Object System.Windows.Forms.Button
$gb_CC_Features = New-Object Windows.Forms.GroupBox
$clb_CC_Features = New-Object System.Windows.Forms.CheckedListBox
$btn_CC_Features_UncheckList = New-Object System.Windows.Forms.Button
$btn_CC_Features_SelectAll = New-Object System.Windows.Forms.Button
$gb_CCF_Description = New-Object Windows.Forms.GroupBox
$tb_CCF_Description = New-Object System.Windows.Forms.TextBox
#Build the form
$frm_BuildConfigurator.Text = "Build Configurator"
$frm_BuildConfigurator.StartPosition = "CenterScreen"
$frm_BuildConfigurator.Width = 380
$frm_BuildConfigurator.Height = 200
$frm_BuildConfigurator.FormBorderStyle = "FixedSingle"
$frm_BuildConfigurator.ControlBox = $false
$frm_BuildConfigurator.Controls.Add($btn_Cancel)
#Set default button behaviour
$frm_BuildConfigurator.KeyPreview = $True
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Enter") {$frm_BuildConfigurator.Close()}})
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$frm_BuildConfigurator.Close()}})
# Create the Cancel button
$btn_Cancel.Location = New-Object System.Drawing.Size(50,145)
$btn_Cancel.Size = New-Object System.Drawing.Size(55,23)
$btn_Cancel.Text = "Cancel"
$btn_Cancel.Add_Click({$frm_BuildConfigurator.Close()})
# Create the Features form elements
$frm_BuildConfigurator.Controls.Add($gb_CC_Features)
$frm_BuildConfigurator.Controls.Add($gb_CCF_Description)
# Create the Features group box
$gb_CC_Features.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation
$gb_CC_Features.Location = New-Object System.Drawing.Point(10,6)
$gb_CC_Features.Name = "gb_CC_Features"
$gb_CC_Features.Size = New-Object System.Drawing.Size(350,132)
$gb_CC_Features.Text = "Features (0 selected)"
$gb_CC_Features.Controls.Add($clb_CC_Features)
$gb_CC_Features.Controls.Add($btn_CC_Features_SelectAll)
$gb_CC_Features.Controls.Add($btn_CC_Features_UncheckList)
$clb_CC_Features.Location = New-Object Drawing.Point 11,16
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.Add_ItemCheck({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_Click({ftnUpdateFeatureSelectionCount})
# Populate the Features checked list box
ForEach ($FeatureItem in $script:xmlFeatures.FeatureList.Feature | Select-Object -Property Name) {
$clb_CC_Features.Items.Add($FeatureItem.Name) | Out-Null
}
# Create the Check All button
$btn_CC_Features_SelectAll.Location = New-Object System.Drawing.Point(250,20)
$btn_CC_Features_SelectAll.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_SelectAll.Text = "Check All"
$btn_CC_Features_SelectAll.Add_Click({ftn_CheckAllItemsInCheckList $clb_CC_Features})
$btn_CC_Features_SelectAll.TabIndex = 2
# Create the Uncheck All button
$btn_CC_Features_UncheckList.Location = New-Object System.Drawing.Point(250,50)
$btn_CC_Features_UncheckList.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_UncheckList.Text = "Uncheck All"
$btn_CC_Features_UncheckList.Add_Click({ftn_UncheckList $clb_CC_Features})
$btn_CC_Features_UncheckList.TabIndex = 2
ftnUpdateFeatureSelectionCount
#Show the Form
$frm_BuildConfigurator.ShowDialog() | Out-Null
}
function ftnUpdateFeatureDescription()
{
$tb_CCF_Description.Text = $script:FeatureDisplayNames[$clb_CC_Features.SelectedIndex]
}
function ftnUpdateFeatureSelectionCount ()
{
$gb_CC_Features.Text = "Features (" + $clb_CC_Features.CheckedItems.Count + " selected)"
}
function BuildFeaturesFile ()
{
# Only runs on Windows Server
$Features = Get-WindowsFeature
$xml = "<xml>"
$NewFeaturesFilePath = $ScriptDir + "\FeaturesNew.xml"
ForEach($Feature in $Features)
{
$xml += "<Feature Name='" + $Feature.Name + "' DisplayName='" + $Feature.DisplayName + "'>"
$xml += "</Feature>"
}
$xml += "</xml>"
$xml | Out-File -FilePath $NewFeaturesFilePath
}
function ReadFeaturesFile ()
{
[array] $script:FeatureNames = $null
[array] $script:FeatureDisplayNames = $null
$FileExists = Test-Path $FeaturesFilePath
if ($FileExists -eq $true) {
$script:xmlFeatures.Load($FeaturesFilePath)
$xml_Features = $script:xmlFeatures.SelectNodes("/FeatureList/Feature")
ForEach ($Feature in $xml_Features) {
[array] $script:FeatureNames += $Feature.Name
[array] $script:FeatureDisplayNames += $Feature.DisplayName
}
}
}
function ftn_UncheckList( $checkedListBoxObject )
{
ForEach($i in $checkedListBoxObject.CheckedIndices) { $checkedListBoxObject.SetItemCheckState($i, 'Unchecked') | Out-Null }
}
function ftn_CheckAllItemsInCheckList( $checkedListBoxObject )
{
#ForEach($i in $checkedListBoxObject.Items) { $checkedListBoxObject.SetItemChecked($checkedListBoxObject.Items.IndexOf($i), $true) }
For($index =0; $index -lt $checkedListBoxItem.Items.Count; $index++){ if($checkedListBoxItem.GetItemChecked($index)) { $checkedListBoxItem.SetItemChecked($i, $true); }}
}
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$script:xmlFeatures = $null; $script:xmlFeatures = New-Object -TypeName XML
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir = Split-Path -parent $ScriptPath
$FeaturesFilePath = $ScriptDir + "\Features.xml"
$BuildScriptStr = $MyInvocation.MyCommand.Definition #Full path - for script name only use $MyInvocation.MyCommand.Name
$noSelectedFeatures = 0
ReadFeaturesFile
BuildForm
# End of script
File: Features.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FeatureList>
<Feature Name="AD-Certificate" DisplayName="Active Directory Certificate Services"/>
<Feature Name="ADCS-Cert-Authority" DisplayName="Certification Authority"/>
<Feature Name="ADCS-Web-Enrollment" DisplayName="Certification Authority Web Enrollment"/>
</FeatureList>
There are a lot of questions :
1)-2) If you want to click once to check a box you have to set the CheckOnClick property on the CheckedListBox. In you case a line can be checked only once it's selected.
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.CheckOnClick = $true
$clb_CC_Features.Add_ItemCheck({})
3) The message ItemCheck append before the line is really checked so in the function you call on this even you have look if the line is going to be cheched or unchecked.
I change
$clb_CC_Features.Add_ItemCheck({ftnChecked})
#$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
#$clb_CC_Features.Add_Click({})
...
$_ represent the value of the event.
function ftnChecked ()
{
if ($_.NewValue -eq 'checked')
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count + 1) + " selected)"
}
else
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count -1) + " selected)"
}
}
4) You can try the following :
function ftn_CheckAllItemsInCheckList #( $checkedListBoxObject )
{
For($index =0; $index -lt $clb_CC_Features.Items.Count; $index++){ $clb_CC_Features.SetItemChecked($index, $true)}
}

How can I get programmatic access to the "Date taken" field of an image or video using powershell?

I'm trying convert a bunch of pictures and videos, but when I convert it to a new format I obviously lose the properties of the original file. I'd like to be able to read the "Date taken" property from the old file and update it on the new one using powershell.
I can't test it right now (don't have any images with XIF data laying around, but I think this should work:
[reflection.assembly]::LoadWithPartialName("System.Drawing")
$pic = New-Object System.Drawing.Bitmap('C:\PATH\TO\SomePic.jpg')
$bitearr = $pic.GetPropertyItem(36867).Value
$string = [System.Text.Encoding]::ASCII.GetString($bitearr)
$DateTime = [datetime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$DateTime
In general, you can access any extended property for a file shown in explorer through the shell GetDetailsOf method. Here's a short example, adapted from another answer:
$file = Get-Item IMG_0386.jpg
$shellObject = New-Object -ComObject Shell.Application
$directoryObject = $shellObject.NameSpace( $file.Directory.FullName )
$fileObject = $directoryObject.ParseName( $file.Name )
$property = 'Date taken'
for(
$index = 5;
$directoryObject.GetDetailsOf( $directoryObject.Items, $index ) -ne $property;
++$index ) { }
$value = $directoryObject.GetDetailsOf( $fileObject, $index )
However, according to the comments on another question, there is no general-purpose mechanism for setting these properties. The System.Drawing.Bitmap class that EBGreen mentioned will work for images, but I'm afraid I also do not know of a .NET option for video files.
This works for me, thanks to the above help and others.
try{
Get-ChildItem C:\YourFolder\Path | Where-Object {$_.extension -eq '.jpg'} |
ForEach-Object {
$path = $_.FullName
Add-Type -AssemblyName System.Drawing
$bitmap = New-Object System.Drawing.Bitmap($path)
$propertyItem = $bitmap.GetPropertyItem(36867)
$bytes = $propertyItem.Value
$string = [System.Text.Encoding]::ASCII.GetString($bytes)
$dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$bitmap.Dispose()
$_.LastWriteTime = $dateTime
$_.CreationTime = $dateTime
}}
finally
{
}
To read and write the "date taken" property of an image, use the following code (building on the answer of #EBGreen):
try
{
$path = "C:\PATH\TO\SomePic.jpg"
$pathModified = "C:\PATH\TO\SomePic_MODIFIED.jpg"
Add-Type -AssemblyName System.Drawing
$bitmap = New-Object System.Drawing.Bitmap($path)
$propertyItem = $bitmap.GetPropertyItem(36867)
$bytes = $propertyItem.Value
$string = [System.Text.Encoding]::ASCII.GetString($bytes)
$dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$dateTimeModified = $dateTime.AddDays(1) # Set new date here
$stringModified = $dateTimeModified.ToString("yyyy:MM:dd HH:mm:ss`0",$Null)
$bytesModified = [System.Text.Encoding]::ASCII.GetBytes($stringModified)
$propertyItem.Value = $bytesModified
$bitmap.SetPropertyItem($propertyItem)
$bitmap.Save($pathModified)
}
finally
{
$bitmap.Dispose()
}