Welcome to MSDN Blogs Sign in | Join | Help

“HeaderFooter (unknown member) : Invalid request.” Error, while setting slide footer text in PowerPoint 2007 via .Net

Symptoms:

While programming PowerPoint 2007 in .Net, when you try setting Footer text for slides, you may encounter the following error message,

ERROR MESSAGE:
============

HeaderFooter (unknown member) : Invalid request.

Cause

This is a known issue and has been reported to the product group

 

Resolution

To workaround this issue, first, we need to set the “Visible” property of the footer object to true and then we need to set the footer text.

Code:

PPTApplication.ActivePresentation.Slides[1].HeadersFooters.Footer.Visible = Microsoft.Office.Core.MsoTriState.msoTrue; //Here PPTApplication is object of PowerPoint.Application
PPTApplication.ActivePresentation.Slides[1].HeadersFooters.Footer.Text = "My Text";

Posted by Obuli Mani J | 0 Comments

Excel: How do you implement Application_Quit event in Excel – In External automation scenario

You may have come across this blog which demonstrates how you can capture Excel’s quit event from an Add-In (in process). This post will be about capturing Excel’s quit event when you are externally automating Excel.

Consider this situation – You have an application that automates Excel and presents Excel window to the user for further manipulation, and probably also captures other Excel events. Now, you may want to execute code after the user has done what he wanted to do and close/quit Excel.

To cater to this need of developers I am presenting this technique, here is how it goes –

After launching Excel process, we get the Excel window handle (Excel Object model exposes this as Application.hWnd). We then traverse through all the (Excel) processes to get the Process object for the Excel that we launched.We then wait till the Process.MainWindowHandle property is zero. Since our application is holding reference(s) to Excel object(s) we cannot say for certain that Excel process will be removed from memory when user closes Excel, and hence the need to look at MainWindowHandle property.

There may be times when Excel is abruptly closed, if someone killed the Excel process. To cater to this, you can add an additional check for Process.hasExited property.

Here are the steps to demonstrate this technique –

1) Create a Windows forms application in VB

2) Add a button in the designer and name it as “Launch and watch excel for quit”

3) Add COM reference to the “Microsoft Excel 12.0 Object Library”

4) Add the imports statement in the Form1.vb file

Imports Excel = Microsoft.Office.Interop.Excel

5) Declare an application object for Excel

Dim oApp As Excel.Application

6) In the click event handler for the button added in step 2 write the following code :

 

oApp = New Excel.Application
oApp.Workbooks.Add()
oApp.Visible = True

'For getting handle for the actual Excel window that we launched
Dim iOurWindowHandle As Integer = oApp.Hwnd

Dim XLProcess As Process = Nothing

'This will hold the Excel process launched by our application
Dim ourXLProcess As Process = Nothing

'For getting the handle to our specific Excel window (the one launched by us)
For Each XLProcess In Process.GetProcessesByName("Excel")
If (iOurWindowHandle = XLProcess.MainWindowHandle.ToInt32()) Then
ourXLProcess = XLProcess
Exit For
End If
Next

If ourXLProcess Is Nothing Then
MessageBox.Show("Could not get hold of the Excel process that was launched")
End If


'Now, we will keep a watch over the Excel process launched by us
'Probably can go in to a separate thread. This currently is a tight loop, blocking the Form UI till Excel is closed.
Dim fXLPresent As Boolean = True

While fXLPresent
ourXLProcess.Refresh()

'HasExited check added scenarios where someone kills Excel process (from task manager)
If (ourXLProcess.HasExited = True) Then
fXLPresent = False
'Check the MainWindowHandle
ElseIf (ourXLProcess.MainWindowHandle = IntPtr.Zero) Then
fXLPresent = False
End If

If fXLPresent = False Then
MessageBox.Show("Excel has quit")
Exit While
End If

System.Threading.Thread.Sleep(2000)
End While

7) Build the project and run it. This will launch a Windows form with a button. On clicking the button the Excel is launched. If you click on the close button on Excel window or kill the process from the task manager we are presented with the MessageBox saying “Excel has quit”

Word 2007: Customizing Document inspector leaves instance of Word open in the memory.

Symptoms:

If you have customized Document inspector for Word 2007 using IDocumentInspector interface, after running the Document Inspector on the document and then exiting out of Word, will leave an instance of the application in memory

 

Cause

When you invoke the Document inspector, you pass in the Document object to the Inspect and the Fix methods. When you close the main document, the document object passed to the IDocumentInspector is still not released and Word stays in the memory and never exits.

This is known issue reported with the Document Inspector.

 

Resolution

To resolve the issue you need to NAR (Null and Release) all the document objects you use in your code. Here’s the article that talks about how to implement this in C# and VB.NET in the following KB article.

Office application does not quit after automation from Visual Studio .NET client

http://support.microsoft.com/?id=317109

Excel 2007, WPF: Events are not fired for items that overlap Excel UI for WPF context Menus

Symptoms:

Consider a scenario where you have a VSTO 3.0 add-in for Excel 2007 that loads up a WPF control in a custom task pane as a context menu.

You have also subscribed to the MenuClick event for each item of the Context Menu. The context menu is displayed but some of the items of menu are displayed over the Excel sheet area as displayed in the below picture :-

Excel

If you click on the item which overlaps the Excel UI, you will notice that the menu click event is not fired for those items. The issue does not reproduce with Windows Form controls.

Cause:

This issue is a known issue reported with WPF.

 

Resolution:

One of the workarounds is to use the DispatcherFrame to pump messages and subscribe to GotFocusEvent and LostFocusEvent for the menu. (Similar to DoEvents method is VB6)

  1. Declare a class level dispatcher frame object 
    System.Windows.Threading.DispatcherFrame _frame;
  2. Subscribe to the GotFocusEvent and LostFocusEvent for the menu
    _menu.AddHandler(System.Windows.UIElement.GotFocusEvent,new RoutedEventHandler(OnGotFocusEvent));
    _menu.AddHandler(System.Windows.UIElement.LostFocusEvent, new RoutedEventHandler(OnLostFocusEvent));

    Below is the implementation for the event procedures for the GotFocusEvent and LostFocusEvent

    private void OnGotFocusEvent(object sender, RoutedEventArgs e)
    {
     if (LogicalTreeHelper.GetParent((DependencyObject)e.OriginalSource) == _menu)
     {
     Dispatcher.BeginInvoke(DispatcherPriority.Normal, (DispatcherOperationCallback)delegate(object unused)
            {
             _frame = new DispatcherFrame();
             Dispatcher.PushFrame(_frame);
             return null;
             }, null);
       }
    }
     
    private void OnLostFocusEvent(object sender, RoutedEventArgs e)
    {
      if (LogicalTreeHelper.GetParent((DependencyObject)e.OriginalSource) == _menu)
      {
         _frame.Continue = false;
      }
    }

Excel 2007: Ribbon tabs (TabAddIns) disappear from doc level customizations if user opens and closes a doc running in the same instance of Excel

Symptoms:

Consider a scenario where you have developed a Document level customization for Excel 2007 using Visual Studio Tools for Office 3.0 and you have customized the default tab (TabAddIns) to add any Office Ribbon control.

You will notice that the “AddIns” tab is no longer available, if you open or create a new Excel workbook in the same instance of the customized Workbook and then re-activate the customized workbook.

Below is how you can reproduce the problem:-

1. Create a new Excel 2007 Document Level Solution.

2. Add a new Ribbon to the solution.

3. Add Office Button to the default group created (group1) in the Ribbon Designer.

4. Run the Solution by pressing F5.

5. Select the Add-Ins tab and you will see the button that you have added

6. Now, Go to File—>New-->Blank Workbook and click on OK or open any other workbook in the same instance of Excel.

7. If you re-activate your customized workbook, you will notice that the AddIns tab is no longer visible.

Cause

This is a known problem with Excel 2007 VSTO workbook customizations.

 

Workaround:

As a workaround you can perform some action which will toggle the Add-Ins tab on and off when the workbook is activated. This will reset the visibility of the Add-Ins tab.  Below code snippet demonstrates the approach.

void Application_WindowActivate(Microsoft.Office.Interop.Excel.Workbook Wb, Microsoft.Office.Interop.Excel.Window Wn)
{
Globals.ThisWorkbook.Application.CommandBars["Standard"].Controls[1].OnAction = "abc";
Globals.ThisWorkbook.Application.CommandBars["Standard"].Controls[1].OnAction = "";
}

How to loop through the app-domains in the current process

Consider a scenario where we have a VSTO add-in and an automation add-in for Excel and we want to share some objects from the VSTO addin to the Automation add-in. Because the VSTO add-in is loaded in its own separate appdomain and the automation add-in is loaded in the different(default) appdomain, sharing objects is not that easy. One way to share objects could be to use "RequestCOMAutomationService" method as described in the following blog article:

http://blogs.msdn.com/andreww/archive/2008/08/11/why-your-comaddin-object-should-derive-from-standardolemarshalobject.aspx

However, by using the above mentioned method, the object would be shared "ByVal". What if we want to share the objects "ByRef"

One of the solution to share objects "ByRef" is to use appdomain's "CreateInstanceAndUnwrap" method to create objects and to get the proxy objects. However to use "CreateInstanceAndUnwrap" method we need reference to the appdomain in which the actual objects will be created.

In our scenario we need to have reference to the VSTO appdomain so that we can create objects in it and can obtain proxy objects in the automation appdomain.

 

Since VSTO appdomain is automatically created by VSTO runtime, we do not have reference to it. We need a way to loop through the appdomain present in the current process (Excel) and get a reference to the VSTO appdomain.

To find the VSTO Appdomain (in a collection of appdomains), we will use its name. To find the VSTO appdomain's name, in the VSTO add-in code, we can use following code:

AppDomain ad = System.Threading.Thread.GetDomain();
String VSTO_AppDomain_Name = ad.FriendlyName.ToString();

This VSTO appdomain name can be shared with the Automation add-in using the RequestCOMAutomationService technique mentioned above or by using any other way.

Although there is no managed API which enables us to loop through the appdomains, there is a way to loop through the app-domains in a process. The following code can be used to loop through the appdomains and to get reference to the VSTO appdomain:

using System.Runtime.InteropServices; 
using mscoree; 
AppDomain VSTOAppDomain = null; 

private void FindVSTOAppDomain(string VSTOAppDomainName) 
{ 
    IntPtr enumHandle = IntPtr.Zero; 
    CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass(); 
    try 
    { 
        host.EnumDomains(out enumHandle); 
        object domain = null; 
        while (true) 
        { 
            host.NextDomain(enumHandle, out domain); 
            if (domain == null) break; 
            AppDomain ad = (AppDomain)domain; 
            if (ad.FriendlyName.Equals(VSTOAppDomainName)) 
            { 
                VSTOAppDomain = ad; 
            } 
        } 
    } 
    catch (Exception ex) 
    { 

    } 
}

 

    To use above code we need to add COM reference to the mscoree.tlb to the project. mscoree.tlb can be located at:
"C:\WINDOWS\Microsoft.NET\Framework\vXXXXXX\mscoree.tlb".

 

For more information, please refer to: http://blogs.msdn.com/jackg/archive/2007/06/11/enumerating-appdomains.aspx

Excel: How do you use RefEdit/range selection control to select a cell range using .Net

In Excel VBA, we could use RefEdit control on a user form for allowing users to select a cell range on worksheets. This control allows users to select a range easily just by dragging on Excel cells and it can be passed as an input (as range object) to the program. It is very beneficial for users while doing Excel programming using VBA, as user does not require typing the address of a range manually.

However, we cannot use this control in other than VBA userforms, because RefEdit is not really a true ActiveX control, it is a special in-process to Excel only control that directly integrates with VBA. As per the following article, the RefEdit Control is designed to work only when placed on a form in an Excel VBA project.

RefEdit control does not work on form in a Com Add-In in Excel

http://support.microsoft.com/?id=281542

As a workaround, some people develop their own user control to achieve this functionality in Excel. However, if we want to use an Excel out of box option to implement such functionality in our .Net applications or any external Excel automation applications, here is what we need to do,

We could make use of Application.Inputbox method that Excel Object Model exposes with the Type argument (which is last argument) set as 8. This allows us to select a cell range, as if we select while using RefEdit control/Function Arguments dialog box, which we get when we insert Excel functions

I have implemented this using the C# code snippet below,

            Excel.Application xlApp = "<Refer to an Excel application instance>";
            //Declares range object
            Microsoft.Office.Interop.Excel.Range rng;

            //Implements InputBox method with last argument value is set as 8
            rng = (Microsoft.Office.Interop.Excel.Range)xlApp.InputBox("Select Range", System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, 8);

            //Display the range address that we just received as an input using InputBox method
            MessageBox.Show(rng.get_Address(System.Type.Missing, System.Type.Missing, Microsoft.Office.Interop.Excel.XlReferenceStyle.xlA1, System.Type.Missing, System.Type.Missing));
Posted by Obuli Mani J | 0 Comments
Filed under: , ,

Excel: How do you implement Application_Quit event in Excel/how do you intercept Excel application Quit

We know Word Object model provides an event handler called Application_Quit, which will be triggered when the application quits, but we don’t have any such events for Excel (even for PowerPoint and some other Office applications). In order to workaround this issue, first thing that will come in our mind is to use an Office COM add-in and implement OnBeginShutdown method (which “IDTExtensibility2” interface exposes). Moreover, this can be implemented for any/multiple Office applications as well. Otherwise, we can even use ThisAddIn_Shutdown method that VSTO Application level add-in provides.

However, all these methods can run some code when the application is being shut down and they do not allow us to control the quitting of the application. Let us assume a scenario, what if we need to intercept the application quit and need to decide whether to quit the application or not based on certain criteria/business rules.

Here I come with an option to use the old style of processing Windows messages that Office applications receive. I have described the steps below to implement this thought for Excel using VB.Net COM Add-in in much simpler way.

Here are the steps:

  1. Create an Excel COM Add-in using VB.Net
  2. In order to process the windows messages that an Office application receives, we need to inherit the Connect class (Connect.vb) of the COM add-in from, System.Windows.Forms.NativeWindow class. This class provides an overridable method called, WndProc(), that passes Windows message as an argument. We can make use of these messages to implement, as we want. To inherit the NativeWindow class in our Connect class, please make sure that you have added reference for System.Windows.Forms
  3. Then, add the following Using statements in the Connect.cs module,
    Imports Extensibility
    Imports System.Runtime.InteropServices
    Imports System.Windows.Forms
    Imports Excel = Microsoft.Office.Interop.Excel
  4. Declare the following constant. It is the value for “Window Close” message, which will be passed by Excel when user clicks close button
    Const WM_CLOSE As Integer = 16
  5. Add the following code in the OnConnection method of the Connect class. The main purpose of using this method is to pass the Excel application’s handle to NativeWindow class so that it can process the messages that the application receives through the WndProc method
    Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
    
    'Type cast the application object to Excel application class
    xlApp = DirectCast(application, Excel.Application)
    
    'Pass the Excel application handle to NativeWindow class in order to process messages that Excel receives
    MyBase.AssignHandle(xlApp.Hwnd)
    
    'To display a message box
    MessageBox.Show("Excel quit detection add-in started!")
    
    End Sub
  6. Copy the following WndProc method implementation to trap the close action in Excel
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    
            'Check if the Window handle passed by this procedure is for Excel application and the message is to indicate closing Excel application
          
      If m.HWnd = xlApp.Hwnd And m.Msg = WM_CLOSE Then
    
    Dim wb As Excel.Workbook
    
                'Prompt Message box to close Excel or not
    Dim res As DialogResult = MessageBox.Show("You are quitting Excel. Do you want to save changes for the workbooks opened?", "Excel Quit", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1)
    
    'If the user response is "Yes" for the message box, then loop through all the workbooks, save and close it
    If res = DialogResult.Yes Then
    For Each wb In xlApp.Workbooks
    wb.Close(True)
    Next
    
    MyBase.WndProc(m) 'process message as default
    
    ElseIf res = DialogResult.No Then 'If no, Loop through all the workbooks and close it, but don't save
    
    For Each wb In xlApp.Workbooks
      wb.Close(False)
    Next
    
    MyBase.WndProc(m) 'process message as default
    
    End If
    
    'If none of the above criteria is not satisfied, Excel will not quit
    Else
    
                MyBase.WndProc(m) 'process message as default
    
    End If
    
    End Sub
  7. Build the add-in and run it. Now, Excel will prompt with a message box, when user attempts to close Excel application by either clicking “X” button or by going to File menu-->Exit or by pressing Alt+F4.
Posted by Obuli Mani J | 0 Comments
Filed under: , ,

Excel: How to run C# code behind with a click of a button on a Worksheet, without VBA code

OLE object controls such as Command button, Checkbox, etc., allow us to call VBA code behind using OnAction property. However, when a situation arises that we have to call .Net code behind, we cannot use OnAction property, because this property requires a VBA macro to be assigned. In such situations, the option that we can immediately think of is to have VBA macro to call the .Net code, which is possible.

There are scenarios such as one where you want to upgrade your VBA add-in (XLA) to .Net COM/Automation add-in (with no VBA layer in between), which does not allow us to use OnAction property, then this blog post can help you. Because this option does not require us to have VBA layer in between and we can call C#.Net code directly.

I have illustrated this idea using an Excel COM add-in that inserts an OLE command button control on Application start up and I will use it to call its button click event written in C# code behind. To know how to build an Office COM add-in by using Visual C# .NET, please refer to the article, http://support.microsoft.com/kb/302901

Here are the steps:

  1. Create an Excel COM Add-in using C#
  2. Add the following Using statements in the Connect.cs class,
    using System;
    using Extensibility;
    using System.Runtime.InteropServices;
    using Excel = Microsoft.Office.Interop.Excel;
    using Office = Microsoft.Office.Core;
    using MSForms = Microsoft.Vbe.Interop.Forms;
    using Microsoft.VisualBasic.CompilerServices;
  3. Add the following code in the OnConnection method of the Connect class. This code will insert a command button onto the Active Worksheet and wire up the Click event for the button, which is written in the C# code behind
    public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
          {
                try
                {
                    
                    xlApp = (Excel.Application)application;
                    wbs = xlApp.Workbooks; //Get the Workbooks collection
                    wbs.Add(Type.Missing); //Add a new workbook
                    wb = xlApp.ActiveWorkbook; 
                    wsht = (Excel.Worksheet)wb.ActiveSheet;
                    //To insert an OLE Object which of type "CommandButton". We need to use the ProgID for the command button, which is "Forms.CommandButton.1"
                    cmdButton = (Excel.Shape)wsht.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, false, false, Type.Missing, Type.Missing, Type.Missing, 200, 100, 100, 100);
                    //We name the command button, we will use it later
                    cmdButton.Name = "btnClick";
                    //In order to access the Command button object, we are using NewLateBinding class as below
                    CmdBtn = (MSForms.CommandButton)NewLateBinding.LateGet((Excel.Worksheet)xlApp.ActiveSheet, null, "btnClick", new object[0], null, null, null);
                    
                    //Set the required properties for the command button
                    CmdBtn.FontSize = 10;
                    CmdBtn.FontBold = true; 
                    CmdBtn.Caption = "Click Me";
                    //Wiring up the Click event
                    CmdBtn.Click += new Microsoft.Vbe.Interop.Forms.CommandButtonEvents_ClickEventHandler(CmdBtn_Click);
                    
                    }
                catch (Exception ex)
                {
    
                    System.Windows.Forms.MessageBox.Show(ex.Message);
                }
           }
  4. Now, we will need to implement the Button Click event handler. Here is the sample code snippet that just displays a message box and writes value to some cells
    void CmdBtn_Click()
    {
                //Adding the event code
                System.Windows.Forms.MessageBox.Show("I am called from C# COM add-in");
                wsht.get_Range("A1", "A10").Value2 = "I am called from C# Add-in";  
    }
  5. Compile and Build the project, which will add some registry entries to notify Office to load the add-in.

    (Note: on machines with Vista or later, you will need to launch Visual Studio in Administrator mode (To do that, right click Visual Studio Icon-->Run as Administrator), as Vista or later would not allow a program to modify registry entries, when UAC is turned on)

  6. Now, the add-in will add a button and upon button click, it will call code from the C# code behind
Posted by Obuli Mani J | 0 Comments
Filed under: , ,

Word: How to do “Drag and Drop” of Content controls from Task Pane to the document

  1. Create a Word VSTO application level project for Word using Visual Studio 2008/2005 (For VS 2005, you need to install VSTO SE). To do that, go to File Menu in VS -->New-->Project-->Expand Office hive-->Select Word 2007 Add-in
  2. Add a user control item (Name it as CTP.cs) to the project and insert a List box (Name it as, lstItems). To add a user control to the project, go to Project Menu-->Add User Control-->Name it as CTP.cs
  3. Add the code snippet below in the ThisAddIn_Startup event (You will see this when you view the code for ThisAddin.cs).  This will add our custom task pane while loading Word
            Microsoft.Office.Tools.CustomTaskPane WCTP; 
            private void ThisAddIn_Startup(object sender, System.EventArgs e)
            {
               //To add our CTP to the Custom Task panes collection of Word. We need to set the Visible property to true, otherwise, by
               //default it is false
    WCTP = (Microsoft.Office.Tools.CustomTaskPane)this.CustomTaskPanes.Add(new CTP(), "Do Drag Drop");
                WCTP.Visible = true;
            }
  4. The code snippet that we need to have in the user control class is as below,
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using Word = Microsoft.Office.Interop.Word;
      
    namespace DragDrop_ContentControls
    {
        public partial class CTP : UserControl
        {
            public CTP()
            {
                InitializeComponent();
            }
            object missing = System.Type.Missing;
            private Rectangle dragBoxFromMouseDown;
     
     
            private void lstItems_MouseDown(object sender, MouseEventArgs e)
            {
                    // Remember the point where the mouse down occurred. The DragSize indicates
                    // the size that the mouse can move before a drag event should be started.                
                    Size dragSize = SystemInformation.DragSize;
     
                    // Create a rectangle using the DragSize, with the mouse position being
                    // at the center of the rectangle.
                    dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
                                                                   e.Y - (dragSize.Height / 2)), dragSize); 
     
            }
     
            private void lstItems_MouseMove(object sender, MouseEventArgs e)
            {
                
                                                                    //To check if the Mouse left button is clicked
    if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
                {
     
                    // If the mouse moves outside the rectangle, start the drag.
                    if (dragBoxFromMouseDown != Rectangle.Empty &&
                        !dragBoxFromMouseDown.Contains(e.X, e.Y))
                    {
                                //Start Drag Drop
                                DoDragDrop("", DragDropEffects.All);
                                Word.ContentControl cntCtrl;
                                
                                //Insert Content Control at the selected range
                                cntCtrl = Globals.ThisAddIn.Application.Selection.Range.ContentControls.Add(Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlText, ref missing);
                                cntCtrl.Range.Text = lstItems.Text; //This is how we set the value to be to populated in the content controls
                                //To set the XPath for custom XML binding for content controls. This is optional
                                string xPath = @"Root\" + lstItems.Text.Replace(" ", "_");
                                cntCtrl.XMLMapping.SetMapping(xPath, "", null); //This is required when you want to bind to a custom XML part
     
                        }
                    }
                
     
            }
     
            private void lstItems_MouseUp(object sender, MouseEventArgs e)
            {
     
                // Reset the drag rectangle when the mouse button is raised.
                dragBoxFromMouseDown = Rectangle.Empty;
     
            }
     
        }
    }
  5. Build the application and Run it to launch Word
  6. When you drag an item from List box in the Task Pane onto the document surface, you will see a content control being added with the caption of the list item name
Posted by Obuli Mani J | 0 Comments
Filed under: , ,

Issue: Change in behavior from Office 2007 RTM to SP2 when Sheet.Unprotect is called

Let’s say you have a password protected Excel workbook, and you are calling following function through VBA in it:

  Sheet.Unprotect Password:=""

If you run this macro in Office 2007 RTM or SP1 version, it will result in a VBA Error; provided the actual password is not an empty string. However, in SP2 it will result in a password input dialog box.

This change of behavior is the result of a design change for SP2 release.

In case you have a code/logic which depends on the handling of VBA Error received in Office 2007 RTM/SP1 for wrong password specified for the above call, you can get the same behavior in SP2 release by passing a dummy password instead of an empty string. 

For example:

  Sheet.Unprotect Password:="dummy"

If the specified string is the correct password then the Sheet will be unprotected without any error or showing the Password input dialog box.

Posted by Manvir Singh | 0 Comments
Filed under: ,

Issue: Formatting individual characters of Data Labels of Charts programmatically in Excel 2007 SP2

If you are working with Charts programmatically in Excel 2007 Service Pack 2, and are trying to set the formatting of individual characters of Data Labels of charts programmatically, you will notice that the formatting gets applied to the whole of Data Label instead of the specific character/range.

Here is a snippet which one would typically be used to set formatting of specific characters:

Sub Test()
    Dim ch As Chart
    Dim series As series
    Dim points As points
    Dim label As DataLabel
    Dim chars As Characters
    Dim font As font
    
    Set ch = ActiveSheet.Shapes(1).Chart
    
    Set series = ch.SeriesCollection(1)
    Set points = series.points
    Set label = points(1).DataLabel
    
    label.Text = label.Text & vbCrLf & "a"
    
    Set chars = label.Characters(3, 1)
    
    Set font = chars.font
    
    font.Size = 24
End Sub

The above code will work as desired (format individual characters) on Excel 2003 and Excel 2007 Service Pack 1 releases.

This behavior is a known bug with Excel 2007 Service Pack 2 release, and the Excel development team is looking into it. Unfortunately, there is no known programmatic workaround possible for this requirement. Character level formatting of Data labels can only be set manually as of now.

Posted by Manvir Singh | 0 Comments
Filed under: , ,

WORD 2003/EXCEL 2003: VSTO Customization added using ServerDocument.AddCustomization method does not add customization if your document contained an embedded VSTO customized Document/WorkBook

PROBLEM DESCRIPTION

Consider this scenario. You are adding VSTO customizations dynamically to Word 2003/Excel 2003 documents using ServerDocument.AddCustomization method.  The ServerDocument.AddCustomization method succeeds. But when you try to open this customized document, the customization does not load. If you have the “VSTO_SUPPRESSDISPLAYALERTS” environment variable set to “0” you will get the following error:  “The customization assembly could not be found or could not be loaded.”

clip_image002

This is a known issue with Word 2003 and Excel 2003 VSTO customizations, containing embedded customized documents. It doesn’t occur with Word 2007 (or Excel 2007) VSTO customization.

How to avoid this?

To workaround this issue, you can add the customization to the outer document using VSTO related document properties.

  • _AssemblyName:  This property contains an asterisk (*). This indicates to the Microsoft Office application that the document has a Visual Studio Tools for Office customization.
  • _AssemblyLocation: This property contains a string that provides details about the deployment manifest for the customization.

For more details on document properties, please visit Custom Document Properties Overview

The _AssemblyLocation custom document property needs to point to the deployment manfiest location. In order to generate the deployment manifest, follow these steps:

  1. Open the Solution that you would like to publish using Visual Studio
  2. Click  on the View, Select Solution Explorer (This will display Solution explorer window)
  3. Right-click the project node in Solution Explorer.
  4. Click Publish on the shortcut menu.
  5. The Publish Wizard appears.
  6. In the Specify the location to publish this application box, type the path to the folder where you want to deploy the solution. Optionally, you can click Browse and navigate to the folder using the dialog box that appears.
  7. Verify that the path is correct, and then click Finish.

The document, assemblies, and manifests are copied to the publish location, with the assemblies and application manifest (<filename>.dll.mainfest) in a subdirectory under the document and deployment manifest (<filename>.application)

For more details please refer to: How to: Deploy Solution Files Using the Publish Wizard (2003 System)

You can add these custom document properties using either of the two approaches:-

  1. Use IPropertyStorage APIs to add the custom properties.
  2. Add the custom properties using Word/Excel Object Model (Please see KB 257757 if you intend to do this task from un-attended environments).

Approach 1: Adding Custom Document Properties programmatically using IPropertyStorage

The other approach that you can use is to add the properties using IPropertyStorage interface. You can use the concepts demonstrated in DSOFile sample, to add the custom document properties. This sample can be downloaded using the link http://support.microsoft.com/kb/224351.

Information about DSOFile:
The Dsofile.dll sample file demonstrates how to use the OLE32 IPropertyStorage interface to access the extended properties of OLE structured storage files. The component converts the data to Automation friendly data types for easier use by high level programming languages such as Visual Basic 6.0, Visual Basic .NET, and C#. The Dsofile.dll sample file is given with full source code and includes sample clients written in Visual Basic 6.0 and Visual Basic .NET 2003 (7.1).Warning The Dsofile.dll, the source code, and the associated samples are provided "as is" without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose. Use at your own risk.

Let’s see how to use DSOFile sample to attach custom properties to the document, but before that we need to ensure that DSOFile.dll is registered on the development machine.  The self-extracting setup installs and registers the DsoFile.dll component in a location that you want.  But, if you move the DLL to another location or to another computer, you have to re-register the DLL before you can use it again. To do this, type regsvr32 [filepath]\dsofile.dll in the Run dialog box on the Start menu (or from elevated rights command prompt in case of Vista).

Let’s start by creating a new windows application project which references DSOFile.dll and adds these properties.

  1. Launch Visual Studio
  2. On the File menu, point to New, and then click Project.
  3. In the Project Types pane, expand Visual C# or Visual Basic, and then Click on Windows.
  4. In the templates pane, select “Windows Form Application”
  5. In the Name box, type AddPropertiesSample
  6. Click OK. (This will open Visual Studio and by default one Window form Form1 will be added to the project)
  7. Click  on the View, Select Solution Explorer (This will display Solution explorer window)
  8. Right click on the References and Select “Add References…” from the drop down menu.
  9. The “Add Reference” dialog will be displayed, Click on the COM tab in the dialog.
  10. Locate “DSO OLE Document Properties Reader 2.1” in the Component Name and Click on OK.
  11. Click  on the View, Select ToolBox (This will display ToolBox)
  12. Expand “All Windows Forms” in the ToolBox
  13. Drag and Drop button control on the Form1, a new control named Button1 will be added to the form.
  14. Double click on the button1 to add button1_Click event, paste the following code
       1:  string fileName = @"C:\temp\Sample.doc";
       2:  object vstoDocumentLocation = @"C:\Temp\WordDocument.application";
       3:   
       4:  DSOFile.OleDocumentPropertiesClass doc = new DSOFile.OleDocumentPropertiesClass();
       5:  doc.Open(fileName, false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
       6:  object assemblyName = "*";
       7:  DSOFile.CustomProperties customProperties = doc.CustomProperties;
       8:  //Check if _AssemblyName property already exists
       9:  try
      10:    {
      11:       DSOFile.CustomProperty nameProp = customProperties["_AssemblyName"];
      12:       nameProp.set_Value(ref assemblyName);
      13:    }
      14:  catch (Exception)
      15:    {
      16:       customProperties.Add("_AssemblyName", ref assemblyName);
      17:    }
      18:  //Check if _AssemblyLocation property already exists
      19:  try
      20:    {
      21:        DSOFile.CustomProperty loc = customProperties["_AssemblyLocation"];
      22:        loc.set_Value(ref assemblyLocation);
      23:    }
      24:  catch (Exception ex)
      25:    {
      26:        customProperties.Add("_AssemblyLocation", ref vstoDocumentLocation);
      27:    }
      28:   
      29:  doc.Save();
      30:  doc.Close(false);

    Approach 2: Add the properties using Word/Excel Object Model:

    One more approach that you can use is to add the properties using Excel/Word object model.

    Let’s start by creating a new windows application project which references Excel/Word libraries and adds these properties using CustomDocumentProperties Object.

    Create an Automation Client for Microsoft Word

    1. Start Visual Studio .NET.
    2. On the File menu, click New, and then click Project. Select Windows Application from the Visual C# Project types. Form1 is created by default.
    3. Add a reference to Microsoft Word Object Library. To do this, follow these steps:
      1. On the Project menu, click Add Reference.
      2. On the COM tab, locate Microsoft Word Object Library, and then click Select.
    4. Click OK in the Add References dialog box to accept your selections. On the View menu, select Toolbox to display the Toolbox, and then add a button to Form1.
    5. Double-click Button1. The code window for the form appears.
    6. In the code window, replace the following code
         1:  private void button1_Click(object sender, EventArgs e)
         2:  {
         3:  }
         4:   
         5:  With:-
         6:   
         7:  private void button1_Click(object sender, EventArgs e)
         8:  {
         9:  Word.Application oWord;
        10:  Word._Document oDoc;
        11:  object oMissing = Missing.Value;
        12:  object oDocBuiltInProps;
        13:  object oDocCustomProps;
        14:  object fileName = @"C:\test.doc";
        15:   
        16:  //Create an instance of Microsoft Word and make it visible.
        17:  oWord = new Word.Application();
        18:  oWord.Visible = true;
        19:   
        20:  //Create a new Document and get the BuiltInDocumentProperties collection.
        21:  oDoc = oWord.Documents.Open(ref fileName,ref oMissing,ref oMissing,
        22:                              ref oMissing,ref oMissing,ref oMissing, 
        23:                              ref oMissing,ref oMissing,ref oMissing, 
        24:                              ref oMissing,ref oMissing,ref oMissing,
        25:                              ref oMissing,ref oMissing,ref oMissing,
        26:                              ref oMissing);
        27:  oDocBuiltInProps = oDoc.BuiltInDocumentProperties;
        28:  Type typeDocBuiltInProps = oDocBuiltInProps.GetType();
        29:   
        30:  //Get the Author property and display it.
        31:  string strName;
        32:  string strValue;
        33:   
        34:  //Add a property/value pair to the CustomDocumentProperties collection.
        35:  oDocCustomProps = oDoc.CustomDocumentProperties;
        36:  object oDocAssemblyNameProp;
        37:  Type typeDocAssemblyNameProp; 
        38:  Type typeDocCustomProps = oDocCustomProps.GetType();
        39:  try
        40:  {
        41:  strName = "_AssemblyName";
        42:  oDocAssemblyNameProp = typeDocCustomProps.InvokeMember("Item", 
        43:                          BindingFlags.Default | BindingFlags.GetProperty, 
        44:                          null, oDocCustomProps, new object[] { strName });
        45:  typeDocAssemblyNameProp = oDocAssemblyNameProp.GetType();
        46:  strValue = typeDocAssemblyNameProp.InvokeMember("Value", 
        47:              BindingFlags.Default | BindingFlags.GetProperty, 
        48:              null, oDocAssemblyNameProp, new object[] { }).ToString();
        49:  }
        50:  catch
        51:  {
        52:  strName = "_AssemblyName";
        53:  strValue = "*";
        54:  object[] oArgs = { strName, false, MsoDocProperties.msoPropertyTypeString, strValue };
        55:  typeDocCustomProps.InvokeMember("Add",
        56:  BindingFlags.Default | BindingFlags.InvokeMethod, null, 
        57:  oDocCustomProps, oArgs);
        58:  }
        59:  try
        60:  {
        61:  strName = "_AssemblyLocation";
        62:  oDocAssemblyNameProp = typeDocCustomProps.InvokeMember("Item", 
        63:  BindingFlags.Default | BindingFlags.GetProperty, 
        64:  null, oDocCustomProps, new object[] { strName });
        65:   
        66:  typeDocAssemblyNameProp = oDocAssemblyNameProp.GetType();
        67:  strValue = typeDocAssemblyNameProp.InvokeMember("Value", 
        68:  BindingFlags.Default | BindingFlags.GetProperty, null, 
        69:  oDocAssemblyNameProp, new object[] { }).ToString();
        70:  }
        71:  catch
        72:  {
        73:  strName = "_AssemblyLocation";
        74:        strValue = @"C:\WordDocument.application"; ;
        75:        object[] oArgs = { strName, false, MsoDocProperties.msoPropertyTypeString, strValue };
        76:        typeDocCustomProps.InvokeMember("Add", 
        77:        BindingFlags.Default | BindingFlags.InvokeMethod, 
        78:        null, oDocCustomProps, oArgs);
        79:  }
        80:   
        81:  oDoc.Save();
        82:  oDocBuiltInProps = null;
        83:  oDocCustomProps = null;
        84:  oDoc.Close(ref oMissing, ref oMissing,ref oMissing);
        85:  oDoc = null;
        86:  oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
        87:  oWord = null;
        88:   
        89:  }
    7. Scroll to the top of the code window, and then add the following lines to the end of the list of using directives:
    8.    1:  using Microsoft.Office.Core;
         2:  using Word = Microsoft.Office.Interop.Word;
         3:  using System.Reflection;
    9. Press F5 to run the application.

    Note:- The DocumentProperties and the DocumentProperty interfaces are late bound interfaces. To use these interfaces, you must treat them like you would an IDispatch interface.

    For more details, please refer to: How to Use Automation to Get and to Set Office Document Properties with Visual C#.

    The next time the document is opened and saved, the Visual Studio Tools for Office runtime attaches the solution assembly to the document, and creates the Runtime Storage Control, if necessary. The Visual Studio Tools for Office runtime also sets the value of the _AssemblyLocation custom document property to the GUID for the Runtime Storage Control.

Presentation Close event is incorrectly fired while issuing Presentation Print Command

Summary

When the print command of a PowerPoint Presentation is issued, the presentation.close event is fired in addition to the Print event・ 

However, the expected behavior is that the presentation.close event should not get fired when the print command is issued.

 

Cause


This is a known issue with PowerPoint 2003/ 2007.

 

Resolution


Either of the solutions mentioned below can be used to work around this issue:


Solution#1:


This solution exploits the fact that there is a temporary presentation created for the purpose of background printing and temporary presentations are not added to the list of open presentations.


So the workaround is to go through the list of presentations and if current presentation is not found there, then it is the temporary print presentation. The Close Event Handler would ignore the event in that case.

The sample code (c#) to implement the workaround is given below:

   1:  void applicationObject_PresentationPrint(Microsoft.Office.Interop.PowerPoint.PresentationPres)
   2:  { 
   3:      MessageBox.Show("applicationObject_PresentationPrint"); 
   4:  }
   5:   
   6:  void applicationObject_PresentationClose(Microsoft.Office.Interop.PowerPoint.PresentationPres)
   7:  {
   8:      if (!IsPrintPres(Pres))
   9:      {
  10:          //Write your code for presentation close event here. It will get executed only when a presentation is being closed.
  11:          MessageBox.Show("applicationObject_PresentationClose");
  12:      }
  13:  }
  14:   
  15:  private bool IsPrintPres(Microsoft.Office.Interop.PowerPoint.Presentation Pres)
  16:  {
  17:      foreach (Microsoft.Office.Interop.PowerPoint.Presentation p in applicationObject.Presentations)
  18:      {
  19:          if (p == Pres)
  20:          {
  21:              return false;
  22:          }
  23:      }
  24:      return true; 
  25:  }

Solution#2:


Explicitly set the background print option to false (either programmatically or manually). The sample code(C#) to programmatically set the background print option to false is given below:

   1:  void applicationObject_AfterPresentationOpen(Microsoft.Office.Interop.PowerPoint.Presentation Pres)
   2:  {
   3:      MessageBox.Show("applicationObject_AfterPresentationOpen");
   4:      Pres.PrintOptions.PrintInBackground = MsoTriState.msoFalse;
   5:  }
   6:   
   7:  void applicationObject_AfterNewPresentation(Microsoft.Office.Interop.PowerPoint.Presentation Pres)
   8:  {
   9:      MessageBox.Show("applicationObject_AfterNewPresentation");
  10:      Pres.PrintOptions.PrintInBackground = MsoTriState.msoFalse;
  11:  }
Posted by Shiv Khare | 0 Comments

How to Sign the SignatureLine using Office Open XML

Introduction

We can use PackageDigitalSignatureManager class available in System.IO.Packaging namespace to sign any part of an Open XML document. However, when we try to sign a document that has a signature line using this class, we will notice that though the document appears as signed, the signature line still appears as “needs to be signed”. In order to have the signature appear in the signature line, we will need to do some additional tasks which are mentioned in the resolution section below.

Steps to Duplicate the Behaviour

  • Create a new word document in Word 2007.
  • Save this document to C:\Test\Test.docx.
  • Insert a Signature Line by going to -> Insert tab-> in the Text group, point to the arrow next to Signature Line, and then click Microsoft Office Signature Line.
  • Use the following code to sign the document

    // ========First function============
    // Entry Point
    private void DigiSign()
    {
    	// Open the Package    
    	using (Package package = Package.Open(<< Word File Location >>))
    	{
    		// Get the certificate
    		X509Certificate2 certificate = GetCertificate();
    		SignAllParts(package, certificate);
    	}
    }
    // ========End Of First function============
    
    // ========Second function============
    
    private static void SignAllParts(Package package, X509Certificate certificate)
    {
    	if (package == null) throw new ArgumentNullException("SignAllParts(package)");
    	List<Uri> PartstobeSigned = new List<Uri>();
    	List<PackageRelationshipSelector> SignableReleationships = new List<PackageRelationshipSelector>();
    
    	foreach (PackageRelationship relationship in package.GetRelationshipsByType(RT_OfficeDocument))
    	{
    		// Pass the releationship of the root. This is decided based on the RT_OfficeDocument 
    		// http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument
    		CreateListOfSignableItems(relationship, PartstobeSigned, SignableReleationships);
    	}
    	// Create the DigitalSignature Manager
    	PackageDigitalSignatureManager dsm = new PackageDigitalSignatureManager(package);
    	dsm.CertificateOption =  CertificateEmbeddingOption.InSignaturePart;         
    	try
    	{
    		dsm.Sign(PartstobeSigned, certificate, SignableReleationships);
    	}
    	catch (CryptographicException ex)
    	{
    		MessageBox.Show(ex.InnerException.ToString());
    	}
    
    }// end:SignAllParts()
    // ========End of Second function============
    
    // ========Third function============
    
    static void CreateListOfSignableItems(PackageRelationship relationship, List<Uri> PartstobeSigned, List<PackageRelationshipSelector> SignableReleationships)
    {
    	// This function adds the releation to SignableReleationships. And then it gets the part based on the releationship. Parts URI gets added to the PartstobeSigned list.
    	PackageRelationshipSelector selector = new PackageRelationshipSelector(relationship.SourceUri, PackageRelationshipSelectorType.Id, relationship.Id);
    	SignableReleationships.Add(selector);
    	if (relationship.TargetMode == TargetMode.Internal)
    	{
    		PackagePart part = relationship.Package.GetPart(PackUriHelper.ResolvePartUri(relationship.SourceUri, relationship.TargetUri));
    		if (PartstobeSigned.Contains(part.Uri) == false)
    		{
    			PartstobeSigned.Add(part.Uri);
    			// GetRelationships Function: Returns a Collection Of all the releationships that are owned by the part.
    			foreach (PackageRelationship childRelationship in part.GetRelationships())
    			{
    				CreateListOfSignableItems(childRelationship, PartstobeSigned, SignableReleationships);
    			}
    		}
    	}
    }
    // ========End of Third function============
    
    // ======== Fourth function============
    static X509Certificate2 GetCertificate()
    {
    	X509Store certStore = new X509Store(StoreLocation.CurrentUser);
    	certStore.Open(OpenFlags.ReadOnly);
    	X509Certificate2Collection certs =X509Certificate2UI.SelectFromCollection(certStore.Certificates,"Select a certificate","Please select a certificate",
    	X509SelectionFlag.SingleSelection);
    	return certs.Count > 0 ? certs[0] : null;
    }
    // ========End of Fourth function============

  • Open the document, you will notice that the doc is signed but signature line still shows as “needs to be signed”.

Resolution

The point is, Sign method expects an XML file of ContentType “System.Security.Cryptography.Xml.DataObject” which should include the GUID of the signatureLine needs to be signed.

Note: Whenever we create a SignatureLine, it gets an unique GUID which we need to use while signing the SignatureLine.

The structure of the XML file should be

<SignatureProperties xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignatureProperty Id="idOfficeV1Details" Target="#idPackageSignature">
    <SignatureInfoV1 xmlns="http://schemas.microsoft.com/office/2006/digsig">
      <SetupID>{3CF6B91E-C5F6-46A4-B036-72597274FCC0}</SetupID>
      <SignatureText>Test User</SignatureText>
      <ManifestHashAlgorithm>http://www.w3.org/2000/09/xmldsig#sha1</ManifestHashAlgorithm>
    </SignatureInfoV1>
  </SignatureProperty>
</SignatureProperties>

Where SetupID  represents the GUID of the SignatureLine.

Once the XML file is created, we can call the Sign method available in PackageDigitalSignatureManager class. Following is the complete listing of the class:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Packaging;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace DigitalSign
{
    public partial class SignDoc
    {
        // Get help about them from http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/%5BMS-OFFCRYPTO%5D.pdf

        static readonly string RT_OfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
        static readonly string OfficeObjectID = "idOfficeObject";
        static readonly string SignatureID = "idPackageSignature";
        static readonly string ManifestHashAlgorithm = "http://www.w3.org/2000/09/xmldsig#sha1";
        
       // Entry Point
        private void DigiSign()
        {
        // Open the Package    
            using (Package package = Package.Open(<< Word File>>)
            {
                // Get the certificate
                X509Certificate2 certificate = GetCertificate();
                SignAllParts(package, certificate);
            }
        }

        private static void SignAllParts(Package package, X509Certificate certificate)
        {
            if (package == null) throw new ArgumentNullException("SignAllParts(package)");
            List<Uri> PartstobeSigned = new List<Uri>();
            List<PackageRelationshipSelector> SignableReleationships = new List<PackageRelationshipSelector>();

            foreach (PackageRelationship relationship in package.GetRelationshipsByType(RT_OfficeDocument))
            {
                // Pass the releationship of the root. This is decided based on the RT_OfficeDocument (http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument)
                CreateListOfSignableItems(relationship, PartstobeSigned, SignableReleationships);
            }
            // Create the DigitalSignature Manager
            PackageDigitalSignatureManager dsm = new PackageDigitalSignatureManager(package);
            dsm.CertificateOption =  CertificateEmbeddingOption.InSignaturePart;
           
            string signatureID = SignatureID;
            string manifestHashAlgorithm = ManifestHashAlgorithm;
            System.Security.Cryptography.Xml.DataObject officeObject = CreateOfficeObject(signatureID, manifestHashAlgorithm);
            Reference officeObjectReference = new Reference("#" + OfficeObjectID);

            try
            {
              dsm.Sign(PartstobeSigned, certificate, SignableReleationships, signatureID, new System.Security.Cryptography.Xml.DataObject[] { officeObject }, new Reference[] { officeObjectReference }); 
            }
            catch (CryptographicException ex)
            {
                MessageBox.Show(ex.InnerException.ToString());
            }

        }// end:SignAllParts()

        /**************************SignDocument******************************/
        //  This function is a helper function. The main role of this function is to 
        //  create two lists, one with Package Parts that you want to sign, the other 
        //  containing PacakgeRelationshipSelector objects which indicate relationships to sign.
        /*******************************************************************/
        static void CreateListOfSignableItems(PackageRelationship relationship, List<Uri> PartstobeSigned, List<PackageRelationshipSelector> SignableReleationships)
        {
            // This function adds the releation to SignableReleationships. And then it gets the part based on the releationship. Parts URI gets added to the PartstobeSigned list.
            PackageRelationshipSelector selector = new PackageRelationshipSelector(relationship.SourceUri, PackageRelationshipSelectorType.Id, relationship.Id);
            SignableReleationships.Add(selector);
            if (relationship.TargetMode == TargetMode.Internal)
            {
                PackagePart part = relationship.Package.GetPart(PackUriHelper.ResolvePartUri(relationship.SourceUri, relationship.TargetUri));
                if (PartstobeSigned.Contains(part.Uri) == false)
                {
                    PartstobeSigned.Add(part.Uri);
                // GetRelationships Function: Returns a Collection Of all the releationships that are owned by the part.
                    foreach (PackageRelationship childRelationship in part.GetRelationships())
                    {
                        CreateListOfSignableItems(childRelationship, PartstobeSigned, SignableReleationships);
                    }
                }
            }
        }
            /**************************SignDocument******************************/
        //  Once you create the list and try to sign it, Office will not validate the Signature.
        //  To allow Office to validate the signature, it requires a custom object which should be added to the 
        //  signature parts. This function loads the OfficeObject.xml resource.
        //  Please note that GUID being passed in document.Loadxml. 
        //  Background Information: Once you add a SignatureLine in Word, Word gives a unique GUID to it. Now while loading the
        //  OfficeObject.xml, we need to make sure that The this GUID should match to the ID of the signature line. 
        //  So if you are generating a SignatureLine programmtically, then mmake sure that you generate the GUID for the 
        //  SignatureLine and for this element. 
        /*******************************************************************/

        static System.Security.Cryptography.Xml.DataObject CreateOfficeObject(
           string signatureID, string manifestHashAlgorithm)
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(String.Format(Properties.Resources.OfficeObject, signatureID, manifestHashAlgorithm, "{3CF6B91E-C5F6-46A4-B036-72597274FCC0}"));
            System.Security.Cryptography.Xml.DataObject officeObject = new System.Security.Cryptography.Xml.DataObject();
            // do not change the order of the following two lines
            officeObject.LoadXml(document.DocumentElement); // resets ID
            officeObject.Id = OfficeObjectID; // required ID, do not change
            return officeObject;
        }
        /********************************************************/

        static X509Certificate2 GetCertificate()
        {
            X509Store certStore = new X509Store(StoreLocation.CurrentUser);
            certStore.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certs =X509Certificate2UI.SelectFromCollection(certStore.Certificates,"Select a certificate","Please select a certificate",
                    X509SelectionFlag.SingleSelection);
            return certs.Count > 0 ? certs[0] : null;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            DialogResult a = openFileDialog1.ShowDialog();
            openFileDialog1.InitialDirectory = "C:\\test";
            textBox1.Text=openFileDialog1.FileName;
        }
  
    }
}

Please note that you need to change the GUID of the signatureLine used in the above sample. Following section shows you to how to get this information for an existing SignatureLine.

How to Check GUID of the SignatureLine

In order to know the GUID of the signatureLine which you have inserted using Word:

  1. Extract the content of the package.
  2. Go to SignatureLine declaration of document.xml. You will see a declaration like:

    <o:signatureline v:ext="edit" id="{3CF6B91E-C5F6-46A4-B036-72597274FCC0}" provid="{00000000-0000-0000-0000-000000000000}" o:suggestedsigner="a" o:suggestedsigner2="a" o:suggestedsigneremail="a" issignatureline="t"/>

Where id attribute stands for the GUID. This GUID needs to be passed in the XML file while signing.

Reference for XML generation

http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/%5BMS-OFFCRYPTO%5D.pdf

 

More Posts Next page »
 
Page view tracker