Copying slides from one Powerpoint Presantation to another with Powershell - 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.

Related

Add an image to Word using PowerShell

As stated in the Title, I would like to add an image into a Word document. Though my goal is to NOT use a path (stating where the image is located).
**Like this: **
$word = New-Object -ComObject Word.Application
$word.visible = $true
$document = $word.documents.Add()
$selection = $word.selection
$newInlineShape = $selection.InlineShapes.AddPicture($path)
But rather using some type of Base64String, so that this skript works with every device, regardless if the image path doesn't exist.
My attempt:
$word = New-Object -ComObject Word.Application
$word.visible = $true
$document = $word.documents.Add()
$selection = $word.selection
$base64ImageString = [Convert]::ToBase64String((Get-Content $path -encoding byte))
$imageBytes = [Convert]::FromBase64String($base64ImageString)
$ms = New-Object IO.MemoryStream($imageBytes, 0, $imageBytes.Length)
$ms.Write($imageBytes, 0, $imageBytes.Length);
$alkanelogo = [System.Drawing.Image]::FromStream($ms, $true)
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $alkanelogo.Size.Width;
$pictureBox.Height = $alkanelogo.Size.Height;
$pictureBox.Location = New-Object System.Drawing.Size(153,223)
$pictureBox.Image = $alkanelogo;
$newInlineShape = $selection.InlineShapes.AddPicture($pictureBox.Image)
Note: The variable "$path" is only here as a placeholder
I've figured it out. I downloaded the image to my local computer, converted it to a base64 string and then back to an image.
So that this script works with every user regardless of there path, I built in it to download the file to a specific path (that I created).
Powershell will then extract the image from the path I created.
$filepath = 'C:\temp\image.png'
$folderpath = 'C:\temp\'
if([System.IO.File]::Exists($filepath -or $folderpath)){
rmdir 'C:\temp\image.png'
$b64 = "AAA..."
$bytes = [Convert]::FromBase64String($b64)
[IO.File]::WriteAllBytes($filepath, $bytes)
}else{
mkdir 'C:\temp\' -ErrorAction SilentlyContinue
$b64 = "AAA..."
$bytes = [Convert]::FromBase64String($b64)
[IO.File]::WriteAllBytes($filepath, $bytes)
}

Spellcheck content of Document in Powershell

I have added spell checking by reading contents of a file using powershell script?
This script does my job, but I want to check if there are any external packages or modules available for the same, since it would make the work easier.
$file = Get-ChildItem ./Code-Duplication/master.md
$Proofread_text = Get-Content $file.FullName
$Word = New-Object -COM Word.Application
$Document = $Word.Documents.Add()
$Textrange = $Document.Range(0)
#$english = FindLanguage("English (US)")
#$Textrange.LanguageID = $english.ID
$Textrange.InsertAfter($Proofread_text)
<#Handle misspelled words here#>
$file.Name
Write-Output "---------------"
foreach($spell_error in $textrange.SpellingErrors){
Write-Host $spell_error.Text
}
$Document.Close(0)
$Word.Quit()

Powershell with Word CheckSpelling and selected dictionary

I would like to spell check some strings using the Microsoft Word API in Powershell and a specific dictionary ("English (US)").
I use the following code to do the checking but it does not seem to take into account the dictionary I want. Any ideas what is wrong? Also, command "New-Object -COM Word.Dictionary" seems to fail.
$word = New-Object -COM Word.Application
$dictionary = New-Object -COM Word.Dictionary
foreach ($language in $word.Languages) {
if ($language.Name.Contains("English (US)")) {
$dictionary = $language.ActiveSpellingDictionary;
break;
}
}
Write-Host $dictionary.Name
$check = $word.CheckSpelling("Color", [ref]$null, [ref]$null, [ref]$dictionary)
if(!$check) {
Write-Host "Spelling Error!"
}
$word.quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
Remove-Variable word
The COMobject word.dictionary does not exist (at least not on my machine), here is what worked for me in the short test i did:
$dic = New-Object -COM Scripting.Dictionary #credits to MickyB
$w = New-Object -COM Word.Application
$w.Languages | % {if($_.Name -eq "English (US)"){$dic=$_.ActiveSpellingDictionary}}
$w.checkSpelling("Color", [ref]$null, [ref]$null, [ref]$dic)
Another possibility in addition to Paul's:
$dictionary = New-Object -COM Scripting.Dictionary
I was experiencing the same problem, that the Word.Application.checkSpelling() method seems to ignore any dictionary passed to it. I worked around the issue creating a Word document, defining a text range, changing the LanguageID of this range to the language I want to proof read against, and then inspecting detected spelling errors. Here is the code:
<#Function which helps to pick the language#>
function FindLanguage($language_name){
foreach($element in $Word.Languages){
if($element.Name -eq $language_name){
$element.Name
return $element
}
}
}
$Proofread_text = "The lazie frog jumpss over over the small dog."
$Word = New-Object -COM Word.Application
$Document = $Word.Documents.Add()
$Textrange = $Document.Range(0)
$english = FindLanguage("English (US)")
$Textrange.LanguageID = $english.ID
$Textrange.InsertAfter($Proofread_text)
<#Handle misspelled words here#>
foreach($spell_error in $textrange.SpellingErrors){
Write-Host $spell_error.Text
}
$Document.Close(0)
$Word.Quit()
The output will be:
>>lazie
>>jumpss
>>over
I found it helpful to disable the language autodetection in word before starting the script. Especially when you plan to switch the language.

How to Open; SaveAs; then Close an Excel 2013 (macro-enabled) workbook from PowerShell4

Doing a search on the above Com operations yields links dating to '09 and even earlier. Perhaps it hasn't changed but I find myself bumping up against errors where 'it is being used by another process.' - even though no Excel app is open on my desktop. I have to reboot to resume.
To be clear - I'm trying to open an existing file; immediately SaveAs() (that much works), add a sheet, Save(); Close() - and then, importantly, repeat that cycle. In effect, I'm creating a few dozen new sheets within a loop that executes the above 'Open Master; SaveAs(); Edit Stuff; Save; Close;
From the examples I've seen this is not a typical workflow for PowerShell. Pasted at the very bottom is my provisional script - pretty rough and incomplete but things are opening what they need to open and adding sheet also works - until I know I have the right way to cleanly close stuff out I'm not worried about the iterations.
I've found a couple examples that address closing:
From http://theolddogscriptingblog.wordpress.com/2012/06/07/get-rid-of-the-excel-com-object-once-and-for-all/
$x = New-Object -com Excel.Application
$x.Visible = $True
Start-Sleep 5
$x.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($x)
Remove-Variable x
And from http://social.technet.microsoft.com/Forums/windowsserver/en-US/24e57b61-e792-40c1-8aff-b0a8205f48ab/updated-opened-excel-using-powershell?forum=winserverpowershell
Set-ItemProperty $path -name IsReadOnly -value $false
$Excel.ActiveWorkBook.Save()
$openfile.Close()
$openfile = $null
$Excel.Quit()
$Excel = $null
[GC]::Collect()
<>
function MakeNewBook($theWeek, $AffID){
$ExcelFile = "C:\csv\InvoiceTemplate.xlsm"
$Excel = New-Object -Com Excel.Application
$Excel.Visible = $True
$Workbook = $Excel.Workbooks.Open($ExcelFile)
$theWeek = $theWeek -replace "C:\\csv\\", ""
$theWeek = $theWeek -replace "\.csv", ""
$theWeek = "c:\csv\Invoices\" +$AffID +"_" + $theWeek + ".xlsm"
$SummaryWorksheet = $Workbook.worksheets.Item(1)
$Workbook.SaveAs($theWeek)
return $Excel
}
function MakeNewSheet($myBook, $ClassID){
$SheetName = "w"+$ClassID
#$Excel = New-Object -Com Excel.Application
#$Excel.Visible = $True
$wSheet = $myBook.WorkSheets.Add()
}
function SaveSheet ($myExcel)
{
#$WorkBook.EntireColumn.AutoFit()
#Set-ItemProperty $path -name IsReadOnly -value $false
$myExcel.ActiveWorkBook.Save()
$openfile= $myExcel.ActiveWorkBook
$openfile.Close()
$openfile = $null
$myExcel.Quit()
$myExcel = $null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($myExcel)
Remove-Variable $myExcel
[GC]::Collect()
}
$theWeek = "C:\csv\wkStart2013-11-04.csv"
$x = Import-Csv $theWeek
foreach ($xLine in $x){
if ($x[0]){
$AffID = $x[1].idAffiliate
$myExcel = MakeNewBook $theWeek $x[1].idAffiliate
$ClassID = $x[1].idClass
MakeNewSheet $myExcel $ClassID
continue
}
SaveSheet($myExcel)
$AffID = $_.$AffID
$wID = $xLine.idClass
#MakeNewSheet($wID)
Echo "$wID"
}
As a follow up after playing around with this issue myself. I geared my solution around Ron Thompson's comment minus the function calls:
# collect excel process ids before and after new excel background process is started
$priorExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
$Excel = New-Object -ComObject Excel.Application
$postExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
# your code here (probably looping through the Excel document in some way
# try to gently quit
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
# otherwise allow stop-process to clean up
$postExcelProcesses | ? { $priorExcelProcesses -eq $null -or $priorExcelProcesses -notcontains $_ } | % { Stop-Process -Id $_ }
My experience has been that the Quit method doesn't work well, especially when looping. When you get the error, instead of rebooting, open up Task Manager and look at the Processes tab. I'm willing to bet you'll see Excel still open -- maybe even multiple instances of it. I solved this problem by using Get-Process to find all instances of Excel and piping them to Stop-Process. Doesn't seem like that should be necessary, but it did the trick for me.
You should not have to keep track of processes and kill them off.
My experience has been that to properly and completely close Excel (including in loops), you also need to release COM references. In my own testing have found removing the variable for Excel also ensures no remaining references exist which will keep Excel.exe open (like if you are debugging in the ISE).
Without performing the above, if you look in Task Manager, you may see Excel still running...in some cases, many copies.
This has to do with how the COM object is wrapped in a “runtime callable wrapper".
Here is the skeleton code that should be used:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$workbook = $excel.Workbooks.Add()
# or $workbook = $excel.Workbooks.Open($xlsxPath)
# do work with Excel...
$workbook.SaveAs($xlsxPath)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
# no $ needed on variable name in Remove-Variable call
Remove-Variable excel
Try this
$filePath = "E:\TSMBackup\TSMDATAPull\ODCLXTSM01_VM.xlsx"
$excelObj = New-Object -ComObject Excel.Application
$excelObj.Visible = $true
$workBook = $excelObj.Workbooks.Open($filePath)
$workSheet = $workBook.Sheets.Item("Sheet1")
$workSheet.Select()
$workBook.RefreshAll()
$workBook.Save()
$excelObj.Quit()

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()
}