Quantcast
Channel: Visual Studio Office Development (VSOD) Support Team
Viewing all 60 articles
Browse latest View live

You may encounter multiple errors while performing large number of object model calls from a VSTO 3.0 Excel add-in

$
0
0

Suppose you have a VSTO 3.0 add-in for Excel, which performs some Excel object model calls. You notice that it works fine with less number of object model calls but starts failing when the number of object model calls is huge.

Eg. You may be iterating/writing through large number of rows(around 1000) in your Workbook.

The most surprising aspect is that you get different error every time and this aspect of multiple errors makes the issue very puzzling.

Some of the errors you may encounter are - 

Type mismatch - DISP_E_TYPEMISMATCH

Out of memory

Operator NOT is not defined

Return argument has an invalid type

  

The reason for the above errors may be that you have encountered an edge case where the LCID proxy feature of VSTO projects for Excel causes exceptions when iterating through large numbers of Excel objects.

The feature Excel1033Proxy creates proxy objects (wrappers) to make calls using the en-US (1033) culture. While doing so, it seems to be leaking these wrapper objects. This in the excessive automation (large number of OM
calls), becomes a significant leak leading to erratic behavior.

 To work around this issue, you can disable the LCID proxy feature. This feature was introduced to allow Excel solutions created by using VSTO to work seamlessly on end user computers that are not running in the en-US locale.

For more details about this feature, see http://msdn.microsoft.com/en-us/library/ms268748.aspx.

To turn off this feature, you can disable it globally for your entire project by going into the AssemblyInfo file and changing the [assembly:ExcelLocale1033(true)] attribute to false. However, you should note that if your
solution is going to be used in locales other than en-US, then the thread locale will be the user’s locale (and not en-US) hence the arguments (such as date time, numbers) being passed to the Excel Object Model calls

need to be in the user’s locale.

This is a known bug in VSTO 3.0 and the good news is that this problem has already been fixed in VSTO 4.0.

 


Troubleshooting common VSTO issues – Part 1

$
0
0

This blog post talks about the common issues related to the VSTO customization failure errors and their possible solutions. Both document as well as application level customizations will be covered here. For the sake of clarity, this blog post is divided into two parts – In first part we will include the VSTO 2.0 common issues and in the second part we will talk about common customization failure issues with VSTO 3.0 and VSTO 4.0.

To see the VSTO related errors for application level projects, use the VSTO_SUPPRESSDISPLAYALERTS environment variable. Customization failure errors are only shown if the VSTO_SUPPRESSDISPLAYALERTS environment variable is set to 0.

 

Common Error 1:

Customization fails to load with the security exception with the message "The customization does not have the required permissions to execute."

 

The call stack is given below:

System.Security.SecurityException: The customization does not have the required permissions to execute.
at
Microsoft.VisualStudio.Tools.Applications.Runtime.AppDomainManagerInternal.HandleOnlineOffline(Exception e, String basePath, String filePath)
at
Microsoft.VisualStudio.Tools.Applications.Runtime.AppDomainManagerInternal.CreateCustomizationDomainInternal(Uri uriFullDocumentDirectory, Uri uriFullDocFilePath, String documentName, IHostServiceProvider hostCallback, IAddinSecurityManager secman, AppManifest& applicationManifest, Boolean& manifestDirty, IAppInfo& appInfo)
at
Microsoft.VisualStudio.Tools.Applications.Runtime.AppDomainManagerInternal.CreateCustomizationDomain(String applicationDomainPath, String documentName, IHostServiceProvider hostCallback, IExecuteCustomization& executor)
at
Microsoft.VisualStudio.Tools.Applications.Runtime.AppDomainManager.CreateCustomizationDomain(String applicationDomainPath, String documentName, IHostServiceProvider hostCallback, IExecuteCustomization& executor)

Solution/Troubleshooting steps:

To deploy and run a VSTO add-in created using VSTO 2005/VSTO SE, you must grant full trust to the assemblies in the security policy of each end user or computer. Please ensure that the add-in assembly and all the dependent assemblies are given full trust. To know more about How to grant permissions to folders and assemblies, go through the following article:

http://msdn.microsoft.com/en-us/library/zdc263t0(v=vs.80).aspx.

 

Common Error 2:

Customization fails to load and the following dialog box is displayed:

Please note that call stack in the above error dialog box is empty.

Solution/Troubleshooting steps:

There could be multiple reasons for this error. To fix it, try the following:

1) Check that the user has access to the assembly location and that the named assembly exists.

2) Check if the appropriate VSTO Runtime is installed on the machine.

3) Check if CAS permissions are correctly assigned to the assemblies, if the project uses any other third party assemblies or any other dlls, these also need to be given full trust.

4) Disable the other add-ins and then retry.

5) Check if a simple add-in works on the problem machine.

6) Check if VBA (for the Office application for which you are developing customization) is installed on the machine. Document level customizations will not work if VBA is not installed.

 

In case of document level customization, open the document in Application manifest editor and check the assembly location. Ensure that the assembly path exists on the machine and is accessible to the user. Assembly location should be given full trust.

In some of the scenarios, everything mentioned above was taken care of, but still the customization was not loaded and the call stack was empty. In such scenarios, there is a possibility that the Runtime storage control (RSC) is corrupt or it has been deleted accidently from the document. Following VBA code can be used to find if VSTO Runtime storage control is present in the document:

Sub FindVSTORuntimeStorageControl()
    Dim intCtr As Integer
    For intCtr = 1 To ActiveDocument.Shapes.Count
        If ActiveDocument.Shapes(intCtr).Type = msoOLEControlObject Then
            If InStr(1, ActiveDocument.Shapes(intCtr).OLEFormat.ProgID, "VSTO.RuntimeStorage") > 0 Then
                  MsgBox "Runtime Storatge Control Found"
           End If
        End If
    Next
End Sub

If it is not there, remove and then re-attach the customization from the document using the Application manifest editor. If the VSTO runtime storage control is present on the document, first delete the “_AssemblyName” and “_AssemblyLocation” properties of the customized document and then delete the runtime storage control using the following VBA code:

Sub DeleteVSTORuntimeStorageControl()
    Dim intCtr As Integer
    For intCtr = 1 To ActiveDocument.Shapes.Count
         If ActiveDocument.Shapes(intCtr).Type = msoOLEControlObject Then
             If InStr(1, ActiveDocument.Shapes(intCtr).OLEFormat.ProgID, "VSTO.RuntimeStorage") > 0 Then
                     ActiveDocument.Shapes(intCtr).Delete
                     ActiveDocument.Save
             End If

         End If
    Next
End Sub

After you have deleted the control, you can re-attach the customization using Application manifest editor.

 

- Sidharth Shah & Ajay Bandooni

Troubleshooting common VSTO issues – Part 2

$
0
0

In this blog post we will talk about some common errors related to VSTO 3.0 and 4.0. For VSTO 2.0 related issues you can check out the blog post Troubleshooting common vsto issues part 1.

Common Error 1:

Customization fails to load while opening a customized document targeting VSTO 3.0/4.0 from a network location with the following exception: 

“CannotCreateCustomizationDomainException: Customization does not have the permissions required to create an application domain. ---> System.Security.SecurityException: Customized functionality in this program will not work because the location of <document> is not in the Office Trusted Locations list, or all trusted locations are disabled.”

The call stack is:

Microsoft.VisualStudio.Tools.Applications.Runtime.CannotCreateCustomizationDomainException: Customization does not have the permissions required to create an application domain. ---> System.Security.SecurityException: Customized functionality in this program will not work because the location of <document> is not in the Office Trusted Locations list, or all trusted locations are disabled. Contact your administrator for further assistance.

at Microsoft.VisualStudio.Tools.Office.Runtime.RuntimeUtilities.VerifyDocumentIsTrusted(String documentFullLocation, String documentName)

at Microsoft.VisualStudio.Tools.Office.Runtime.DomainCreator.CreateCustomizationDomainInternal(String documentFullLocation, String documentName, String assemblyLocation, Boolean showUIDuringDeployment, IntPtr hostServiceProvider, IntPtr& executor)

Solution:

VSTO 3.0 onwards, if you are opening a document level customization from a network share, you need to add that network share to Office Trusted Locations list.

To know how to add a network location to trusted location list, visit:

http://office.microsoft.com/en-us/word-help/create-remove-or-change-a-trusted-location-for-your-files-HA010031999.aspx

 

Common Error 2:

You get the following exception while opening a VSTO 4.0 customization configured to run in VSTOLocal mode against UNC paths:

“CannotCreateCustomizationDomainException: Customization does not have the permissions required to create an application domain. ---> System.Security.SecurityException: Customized functionality in this application will not work because the administrator has listed file:///<path>.vsto as untrusted.”

And the call stack is:

Microsoft.VisualStudio.Tools.Applications.Runtime.CannotCreateCustomizationDomainException: Customization does not have the permissions required to create an application domain. ---> System.Security.SecurityException: Customized functionality in this application will not work because the administrator has listed file:///<path>.vsto as untrusted. Contact your administrator for further assistance.

at Microsoft.VisualStudio.Tools.Office.Runtime.RuntimeUtilities.VerifySolutionUri(Uri uri)

at Microsoft.VisualStudio.Tools.Office.Runtime.DomainCreator.CreateCustomizationDomainInternal(String solutionLocation, String manifestName, String documentName, Boolean showUIDuringDeployment, IntPtr hostServiceProvider, IntPtr& executor)

Solution:

VSTO 4.0 projects configured to run in VSTOLocal mode against UNC paths do not honour the “EnableVSTOLocalUNC” registry key on 64-bit systems. The VSTO Runtime attempts to read the key from the 64-bit VSTOR registry hive instead of the 32-bit VSTOR registry hive.

To fix this issue, create an EnableVSTOLocalUNC key in the 64-bit hive:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Vsto
Runtime Setup\v4]

"EnableVSTOLocalUNC"=dword:00000001

 For more information see KB 2022442.

 

Common Error 3:

VSTO 3.0/4.0 document level customization fails to install with the following exception:

“System.Security.SecurityException: Customized functionality in this application will not work because the certificate used to sign the deployment manifest for <name of the document> or its location is not trusted.”

The call stack is:

System.Security.SecurityException: Customized functionality in this application will not work because the certificate used to sign the deployment manifest for <name of the document> or its location is not trusted. Contact your administrator for further assistance.

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInTrustEvaluator.VerifyTrustPromptKeyInternal(ClickOnceTrustPromptKeyValue promptKeyValue, DeploymentSignatureInformation signatureInformation, String productName)

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInTrustEvaluator.VerifyTrustUsingPromptKey(Uri manifest, DeploymentSignatureInformation signatureInformation, String productName)

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.VerifySecurity(ActivationContext context, Uri manifest, AddInInstallationStatus installState)

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.InstallAddIn()

Solution:

Ensure that the ClickOnce trust prompt is enabled for the zone from which you are trying to load the customization. Navigate to the below registry key and ensure that the PromptingLevel is set to Enabled for the zone from where you are trying to load the customization.

On 32 bit machines:

HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\.NETFramework\Security\TrustManager\PromptingLevel

On 64 bit machines:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\MICROSOFT\.NETFramework\Security\TrustManager\PromptingLevel

For more information, visit How to: Configure Inclusion List Security.

If you are installing the customization from a web site, then make sure that the site is added to the trusted sites.

 

Common Error 4:

You get the following exception while installing the VSTO 3.0/4.0 solution:

“System.Deployment.Application.DeploymentDownloadException: Downloading file:///<path of vsto file> did not succeed.”

Call stack is similar to:

System.Deployment.Application.DeploymentDownloadException: Downloading file:///<path of vsto file> did not succeed. ---> System.Net.WebException: Could not find file '<path of vsto file>'. ---> System.Net.WebException: Could not find file '<path of vsto file>'. ---> System.IO.FileNotFoundException: Could not find file '<path of vsto file>'. File name: '<path of vsto file>'.

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)

at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)

at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)

at System.Net.FileWebStream..ctor(FileWebRequest request, String path, FileMode mode, FileAccess access, FileShare sharing, Int32 length, Boolean async)

at System.Net.FileWebResponse..ctor(FileWebRequest request, Uri uri, FileAccess access, Boolean asyncHint)

--- End of inner exception stack trace --

at System.Net.FileWebResponse..ctor(FileWebRequest request, Uri uri, FileAccess access, Boolean asyncHint)

at System.Net.FileWebRequest.GetResponseCallback(Object state)

--- End of inner exception stack trace ---

at System.Net.FileWebRequest.EndGetResponse(IAsyncResult asyncResult)

at System.Net.FileWebRequest.GetResponse()

at System.Deployment.Application.SystemNetDownloader.DownloadSingleFile(DownloadQueueItem next)

--- End of inner exception stack trace ---

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.GetManifests(TimeSpan timeout)

at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.InstallAddIn()

Solution:

This issue is generally seen when the VSTO solution is trying to get certificate information (publisher name and other data) from Domain Controller and it times out. To resolve the issue, please install this hotfix :- KB 981574.

 

Common Error 5:

You get following exception when you try to use VSTO 3.0/4.0 customization:

“CannotCreateCustomizationDomainException: Customization could not be loaded because the application domain could not be created. ---> System.IO.FileLoadException: Loading this assembly would produce a different grant set from other instances. (Exception from HRESULT: 0x80131401)”

The call stack is:

Microsoft.VisualStudio.Tools.Applications.Runtime.CannotCreateCustomizationDomainException: Customization could not be loaded because the application domain could not be created. ---> System.IO.FileLoadException: Loading this assembly would produce a different grant set from other instances. (Exception from HRESULT: 0x80131401)

at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandle& ctor, Boolean& bNeedSecurityCheck)

at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean fillCache)

at System.RuntimeType.CreateInstanceImpl(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean fillCache)

at System.Activator.CreateInstance(Type type, Boolean nonPublic)

at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)

at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)

at System.Activator.CreateInstance(String assemblyName, String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityInfo, StackCrawlMark& stackMark)

Solution/Troubleshooting steps:

This issue is a bug with .NET CLR. This issue has been fixed in KB 981574. To resolve the issue, please install this hotfix :- KB 981574

 

- Sidharth Shah & Ajay Bandooni

Considerations for using Dsofile on 64 bit OS

$
0
0

This post is by Yeshwanth Channaraj.

There is a well-documented sample program, DSOFile, that enables reading and writing Office document properties (both old format files like *.xls, *.doc and *.ppt, as well as the new open xml formats like *.xlsx, *.docx and *.pptx). The DSOFile sample is compiled as 32 bit.

If you are using this sample in a 32 bit application on a machine with Office 2007 SP2 , Windows 64 bit, then DsoFile will not be able to fetch the properties of Open Xml format files.  This is because Office 2007 SP2 did not ship the 32 bit version of msoshext.dll (shell extension handler) which is the component DSOFile uses to read/write properties from Open XML files . 

This issue is fixed in hotfix KB 2483216. This is also included in the Office 2007 Cumulative Update for February 2011 and subsequently in Office 2007 SP3.

The Office 2010 version of he hotfix is KB 2483230. This is also included in Office 2010 Cumulative Update for February 2011 and subsequently in Office 2010 SP1

If you wish to use DSOFile from a 64 bit program, then you should recompile the DSOFile to target for 64 bit.

An alternative approach to using Dsofile would be to use Open Xml SDK (or System.IO.Packaging). A sample that demonstrates this is given below :-

// *****************************************************************************
// This sample is provided "AS IS" with no warranty or support from Microsoft.
// It is a demonstration, provided for informational purposes only, and has not been rigorously tested with all environments and does not contain error handling.
// It is up to you to make it "production ready" if you use it in any development solution.
// *****************************************************************************


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using System.Xml.Linq;

namespace DocPropertiesOOX
{
class Program
{
static void Main(string[] args)
{
string filePath = @"C:\Users\test\Desktop\testzip.xlsx";
try
{
using (SpreadsheetDocument package = SpreadsheetDocument.Open(filePath,false))
{

CoreFilePropertiesPart coreFileProperties = package.CoreFilePropertiesPart;

System.IO.Stream stream = coreFileProperties.GetStream();

XDocument xdoc = XDocument.Load(coreFileProperties.GetStream());

// Display default properties
IEnumerable<XElement> awElements = from el in xdoc.Descendants()
where el.Name.Namespace == "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
select el;
foreach (XElement el in awElements)

Console.WriteLine(el.Name.LocalName + " = " + el.Value.ToString());

Console.ReadLine();
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
}
}

VB6 based add-ins may fail to work in Office 2013

$
0
0

VB6 based add-ins have a dependency on the Add-In Designer Object Library (msaddndr.dll), which is no longer shipped with Office 2013 (http://technet.microsoft.com/en-us/library/cc178954.aspx see under OSF section). If the add-in setup project did not include this DLL, then the Add-in registration would fail.

To resolve this issue :-

1. The add-in developer can include the msaddndr.dll file to the setup project, and redeploy the add-in. If VB6 Package and Deployment wizard was used, this can be done as shown here :- http://support.microsoft.com/kb/192136. And yes, this file can be redistributed.

Before deployment, make sure the VB6 developer machine has VB6 SP6 installed :-
http://www.microsoft.com/en-us/download/details.aspx?id=5721
and has the latest version of the msaddndr.dll from this cumulative update:-
http://support.microsoft.com/kb/957924

2. If there is no option for the setup project to be rebuilt to include this file, then this file can be copied from a machine which has earlier version of Office or VB6 installed and registered using regsvr32 command. You can then re-run the add-in setup. The msaddndr.dll file can usually be found at this location :-
C:\Program Files\Common Files\DESIGNER
or in case of 64 bit OS under
C:\Program Files (x86)\Common Files\DESIGNER

For the long term, if you are the developer of the add-in, please consider moving to newer supported ways of writing add-ins, such as Apps for Office or VSTO.

Update: A new KB article has been published about this issue :- A custom add-in that uses interfaces in the Msaddndr.dll file does not work in Office 2013

Customizing Office 2013 backstage

$
0
0

Office 2013 introduced many changes to the backstage. In this blog post we will see how Office 2013 backstage can be customized using the backstage XML. The list of idMso’s for Office 2013 can be downloaded from: http://www.microsoft.com/en-us/download/details.aspx?id=36798.

We’ll modify the blog post based on your feedbacks.

Let’s get started with the idMso(s) of the common commands in Office 2013:

Now, let’s have a look at some of the common backstage customization scenarios and the XML used:

Adding a custom task in the “Open” tab

To achieve the above, following XML can be used:

<tab idMso="TabRecent">
<firstColumn>
<taskFormGroup idMso="GroupOpenPlaces">
<category idMso="ButtonCategoryRecentDocuments">
<task id="myTaskFirst" insertAfterMso="ButtonTaskRecentDocuments" label="Custom Task">
<group id="myGroup" label="Custom functionality" helperText="This group contains custom functionality.">
                <primaryItem>
                                <button id="myButton" label="My Button" onAction="CallMe"/>
                </primaryItem>
</group>
</task>
</category>
</taskFormGroup>
</firstColumn>
</tab>

Adding custom task in “Save As” tab

 

Below XML can be used to add custom task in the “Save As” tab:

<tab idMso="TabSave">
<firstColumn>
<taskFormGroup idMso="SaveGroup">
<category idMso="Save">
<task id="myCustomTask" label="My Custom Task" insertAfterMso="ButtonTaskDynamicServiceProvider">
                <group id="myGroupInTabSave" label="Custom functionality" helperText="This group contains custom functionality.">
                                <primaryItem>
                                                <button id="myButton" label="My Button" onAction="CallMe" />
                                </primaryItem>
                </group>
</task>
</category>
</taskFormGroup>
</firstColumn>
</tab>

Adding a button under the existing task in the “Export” tab:

 Use the below XML to achieve the above:

<tab idMso="TabPublish" enabled="true">
    <firstColumn>
        <taskFormGroup idMso="PublishGroup">
            <category idMso="FileTypes">
               <task idMso="FileSaveAsPdfOrXps">
                   <group id="grpMy" insertAfterMso="GroupFileSave" label="Custom functionality" helperText="This group contains the custom functionality.">
                       <primaryItem>
                           <button id="myButtonExport" label="Custom Export Button" />
                       </primaryItem>
                  </group>
              </task>
           </category>
       </taskFormGroup>
    </firstColumn>
</tab>

 Adding a custom button under the Info tab

  

And following is the XML used:

<tab idMso="TabInfo">
                <firstColumn>
                                <group id="TestGrp" label="Custom functionality"  insertBeforeMso="GroupPermissions" helperText="This group contains the custom functionality.">
                                                <primaryItem>
                                                <button id="MyCustomButton" label="My Button" />
                                                </primaryItem>
                                </group>
                </firstColumn>
</tab>

Adding a custom task under share tab and hiding the “Email” and “Post to Blog” tasks

Following is the XML:

<tab idMso="TabShare">
                <firstColumn>
                                <taskFormGroup idMso="GroupShare">
                                                <category idMso="Share">
                                                                <task idMso="SendUsingEmail" visible="false" />
                                                                <task idMso="FileNewBlogPost" visible="false" />
                                                                <task id="myTask" insertBeforeMso="ShareWithPeople" label="Custom task">
                                                                                <group id="MyGrpInTabShare" label="Custom functionality" helperText="This group contains the custom functionality.">
                                                                                                <primaryItem>
                                                                                                                <button id="MyButtonInTabShare" label="Custom Button" />
                                                                                                </primaryItem>
                                                                                </group>
                                                                </task>
                                                </category>
                                </taskFormGroup>
                </firstColumn>
</tab>

 

 

 

 

 

 

 

 

 

Office automation through VC++ using MFC Class from TypeLib results into large number of compilation errors

$
0
0

When trying to Automate Excel, Word, etc using VC++ (MFC based application) in Visual Studio 2010 we generally perform the following steps –

 

1-Go to Visual Studio

2-Click New Project

3-Expand Visual C++ tree node

4-Choose MFC

5-Select MFC Application

6-Enter project name, click OK

7-Choose a dialog based project

8-Click Finish

9-The bare bone project is created

 

In order to add an MFC Class from Typelib, we do the following –

10-Right click project -> Add -> Class

11-Select “MFC Class from TypeLib” , say Add

12-In the list of available type libraries, choose Microsoft Excel 14.0 Object Library

13-Then choose the list of classes you are planning to use in
the project, eg, _Application, _Workbook etc

Once you are done, you will see the relevant header files
being added – eg- CApplication.h, CWorkbook.h etc

Now in order to automate, you will be required to include
the appropriate header file in your projects cpp file, eg, TestDlg.cpp

Eg, #include "CApplication.h"

      #include "CWorkbook.h"

  

Now begins the trouble -

As soon as you include the respective header files, you will
be presented with a huge number of errors (300+ errors) being thrown during
compilation.

Some errors may appear like –

error C2011: 'Picture' : 'struct' type redefinition 

syntax error : identifier 'MsoRGBType'

syntax error : '<L_TYPE_raw>'

etc, etc, etc

 

In order to resolve these mean errors, you need to do a small step –

Go to the top of each header file and locate the following
line

#import "C:\\Program Files(x86)\\Microsoft Office\\Office14\\EXCEL.EXE" no_namespace

 

Comment this line for each Excel(Office) related header files individually in order to get rid of the compilation errors.

 

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

$
0
0

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 AsInteger = 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)
ForEach XLProcess In Process.GetProcessesByName("Excel")
If (iOurWindowHandle = XLProcess.MainWindowHandle.ToInt32()) Then
ourXLProcess = XLProcess
ExitFor
EndIf
Next

If ourXLProcess IsNothingThen
MessageBox.Show("Could not get hold of the Excel process that was launched")
EndIf


'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 AsBoolean = 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
EndIf

If fXLPresent = FalseThen
MessageBox.Show("Excel has quit")
ExitWhile
EndIf

System.Threading.Thread.Sleep(2000)
EndWhile

 

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”


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

$
0
0

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";


How to Delete a Worksheet from Excel using Open XML SDK 2.0

$
0
0

Update (8 Feb 2013):- The sample code has been fixed for issues where it did not correctly delete all defined names and calculation cells.

Recently I worked on a scenario where a user wanted to delete a worksheet from a workbook using Open XML SDK 2.0. The worksheet may  contains some formulas, defined names, pivot tables etc…  Though MSDN provides a code snippet which explains how to delete a worksheet, it doesn’t cover the scenarios when we have formula, defined names, pivot tables etc. If you delete the worksheet following the MSDN article, Excel may not open the workbook and would throw an error.This blog post demonstrates how to delete a worksheet so that the Workbook opens without errors.

A worksheet in a workbook, apart from its part, also contains other dependent parts, entries inside the workbook. We need to delete the dependent/linked parts and the dependencies along with deleting the worksheet part to be able to completely/correctly delete the worksheet.

Here is the sample code which deletes the formula, defined names, pivot tables, CalculationChain associated with the worksheet being deleted. This probably is not covering all sorts of dependencies that a sheet can have, so you may still see errors even after using this code. In such a case, I encourage you to open the workbook in Visual Studio 2008 (using the cool Visual Studio 2008 power tools plugin) and look for any other traces of the worksheet that may be left over. If you do find something that is currently not covered by the code below, please leave me a comment on this post and I will try to incorporate that.

privatevoid DeleteAWorkSheet(string fileName, string sheetToDelete)
{
    string Sheetid = "";
    //Open the workbookusing (SpreadsheetDocument document = SpreadsheetDocument.Open(fileName, true))
    {
        WorkbookPart wbPart = document.WorkbookPart;

        // Get the pivot Table Parts
        IEnumerable<PivotTableCacheDefinitionPart> pvtTableCacheParts = wbPart.PivotTableCacheDefinitionParts;
        Dictionary<PivotTableCacheDefinitionPart, string> pvtTableCacheDefinationPart = new Dictionary<PivotTableCacheDefinitionPart, string>();
        foreach (PivotTableCacheDefinitionPart Item in pvtTableCacheParts)
        {
            PivotCacheDefinition pvtCacheDef = Item.PivotCacheDefinition;
            //Check if this CacheSource is linked to SheetToDelete
            var pvtCahce = pvtCacheDef.Descendants<CacheSource>().Where(s => s.WorksheetSource.Sheet == sheetToDelete);
            if (pvtCahce.Count() > 0)
            {
                pvtTableCacheDefinationPart.Add(Item, Item.ToString());
            }
        }
        foreach (var Item in pvtTableCacheDefinationPart)
        {
            wbPart.DeletePart(Item.Key);
        }
        //Get the SheetToDelete from workbook.xml
        Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().Where(s => s.Name == sheetToDelete).FirstOrDefault();
        if (theSheet == null)
        {
            // The specified sheet doesn't exist.
        }
        //Store the SheetID for the reference
        Sheetid = theSheet.SheetId;

        // Remove the sheet reference from the workbook.
        WorksheetPart worksheetPart = (WorksheetPart)(wbPart.GetPartById(theSheet.Id));
        theSheet.Remove();

        // Delete the worksheet part.
        wbPart.DeletePart(worksheetPart);

        //Get the DefinedNames
        var definedNames = wbPart.Workbook.Descendants<DefinedNames>().FirstOrDefault();
        if (definedNames != null)
        {
            List<DefinedName> defNamesToDelete = new List<DefinedName>();

            foreach (DefinedName Item in definedNames)
            {
                // This condition checks to delete only those names which are part of Sheet in questionif (Item.Text.Contains(sheetToDelete + "!"))
                    defNamesToDelete.Add(Item);
            }

            foreach (DefinedName Item in defNamesToDelete)
            {
                Item.Remove();
            }

        }
        // Get the CalculationChainPart //Note: An instance of this part type contains an ordered set of references to all cells in all worksheets in the //workbook whose value is calculated from any formula

        CalculationChainPart calChainPart;
        calChainPart = wbPart.CalculationChainPart;
        if (calChainPart != null)
        {
            var calChainEntries = calChainPart.CalculationChain.Descendants<CalculationCell>().Where(c => c.SheetId == Sheetid);
            List<CalculationCell> calcsToDelete = new List<CalculationCell>();
            foreach (CalculationCell Item in calChainEntries)
            {
                calcsToDelete.Add(Item);
            }

            foreach (CalculationCell Item in calcsToDelete)
            {
                Item.Remove();
            }

            if (calChainPart.CalculationChain.Count() == 0)
            {
                wbPart.DeletePart(calChainPart);
            }
        }

        // Save the workbook.
        wbPart.Workbook.Save();
    }
}

 

How to set the editing restrictions in Word using Open XML SDK 2.0

$
0
0

Recently I worked on a scenario where customer wanted to set the Document Editing Restrictions in Word 2007 using Open XML SDK 2.0. Document Editing Restrictions is nothing but Review | Protect Documents | Restrict Formatting and Editing option in MS Word 2007. It allows you to set different types of protection like  Readonly, Track changes, Comments and Filling in forms.

Using OpenXML SDK, this protection can be applied to a document using DocumentProtection tag. Once it is applied via Open XML, it can be found in word\settings.xml package part. It will look like this:

<w:documentProtection
w:edit="forms" // Specifies the set of editing restrictions which shall be enforced on a given WordprocessingML document
w:enforcement="1" // Specifies if the document protection settings shall be enforced for a given WordprocessingML document
w:cryptProviderType="rsaFull" // Specifies the type of cryptographic provider to be used
w:cryptAlgorithmClass="hash" // Specifies the class of cryptographic algorithm used by this protection
w:cryptAlgorithmType="typeAny" // Specifies the type of cryptographic algorithm used by this protection
w:cryptAlgorithmSid="4" // Specifies the specific cryptographic hashing algorithm which shall be used along with the salt    
                                          // attribute and user-supplied password in order to compute a hash value for comparison.
w:cryptSpinCount="50000" // Specifies the number of times the hashing function shall be iteratively run
w:hash="0AMSgIVdSif6F5unNC/Lk3rBvr4=" // Specifies the hash value for the password stored with this document
w:salt="m3sJnUyPgf0hUjz+U1Sdxg==" // Specifies the salt which was prepended to the user-supplied password before it was hashed using the    
                                        // hashing algorithm
/>

 

An important point to note here is that, document protection is a set of restrictions used to prevent unintentional changes to all or part of a WordprocessingML document - since this protection does not encrypt the document, malicious applications may circumvent its use. This protection is not intended as a security feature and may be ignored. Detailed description of this element can be found in below mentioned articles:

  1. ECMA 376 - Part 4 - 2.15.1.28 documentProtection (Document Editing Restrictions)
  2. DocumentProtection Class (DocumentFormat.OpenXml.Wordprocessing)

Let’s have a look at the DocumentProtection tag and how to use it. Basically this tag requires a password hash and a salt. (The algorithm is very well explained in the above articles). There are certain steps which are not explained there but they are documented under “Implementation Notes List” in article #1 above. Here is the sample code which demonstrate a way to add the documentProtection to a Word document. The comment provided in the code is directly taken from algorithm explained in article #2, so you can easily match and understand what’s going on.

int[] InitialCodeArray = { 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3 };int[,] EncryptionMatrix = newint[15, 7] {/* char 1 */ {0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09},/* char 2 */ {0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF},/* char 3 */ {0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0},/* char 4 */ {0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40},/* char 5 */ {0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5},/* char 6 */ {0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A},/* char 7 */ {0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9},/* char 8 */ {0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0},/* char 9 */ {0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC},/* char 10 */ {0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10},/* char 11 */ {0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168},/* char 12 */ {0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C},/* char 13 */ {0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD},/* char 14 */ {0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC},/* char 15 */ {0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4} }; Functions: privatebyte[] concatByteArrays(byte[] array1, byte[] array2) {byte[] result = newbyte[array1.Length + array2.Length]; Buffer.BlockCopy(array2, 0, result, 0, array2.Length); Buffer.BlockCopy(array1, 0, result, array2.Length, array1.Length);return result; } // Main implementation publicvoid ApplyDocumentProtection(WordprocessingDocument wdDocument, string strPassword) { // Generate the Saltbyte[] arrSalt = newbyte[16]; RandomNumberGenerator rand = new RNGCryptoServiceProvider(); rand.GetNonZeroBytes(arrSalt);//Array to hold Key Valuesbyte[] generatedKey = newbyte[4];//Maximum length of the password is 15 chars.int intMaxPasswordLength = 15; if (!String.IsNullOrEmpty(strPassword)) {// Truncate the password to 15 characters strPassword = strPassword.Substring(0, Math.Min(strPassword.Length, intMaxPasswordLength));// Construct a new NULL-terminated string consisting of single-byte characters:// -- > Get the single-byte values by iterating through the Unicode characters of the truncated Password.// --> For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.byte[] arrByteChars = newbyte[strPassword.Length];for (int intLoop = 0; intLoop < strPassword.Length; intLoop++) {int intTemp = Convert.ToInt32(strPassword[intLoop]); arrByteChars[intLoop] = Convert.ToByte(intTemp & 0x00FF);if (arrByteChars[intLoop] == 0) arrByteChars[intLoop] = Convert.ToByte((intTemp & 0xFF00) >> 8); }// Compute the high-order word of the new key:// --> Initialize from the initial code array (see below), depending on the strPassword’s length. int intHighOrderWord = InitialCodeArray[arrByteChars.Length - 1];// --> For each character in the strPassword:// --> For every bit in the character, starting with the least significant and progressing to (but excluding) // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from // the Encryption Matrixfor (int intLoop = 0; intLoop < arrByteChars.Length; intLoop++) {int tmp = intMaxPasswordLength - arrByteChars.Length + intLoop;for (int intBit = 0; intBit < 7; intBit++) {if ((arrByteChars[intLoop] & (0x0001 << intBit)) != 0) { intHighOrderWord ^= EncryptionMatrix[tmp, intBit]; } } }// Compute the low-order word of the new key:// Initialize with 0int intLowOrderWord = 0;// For each character in the strPassword, going backwardsfor (int intLoopChar = arrByteChars.Length - 1; intLoopChar >= 0; intLoopChar--) {// low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character intLowOrderWord = (((intLowOrderWord >> 14) & 0x0001) | ((intLowOrderWord << 1) & 0x7FFF)) ^ arrByteChars[intLoopChar]; }// Lastly,low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. intLowOrderWord = (((intLowOrderWord >> 14) & 0x0001) | ((intLowOrderWord << 1) & 0x7FFF)) ^ arrByteChars.Length ^ 0xCE4B;// Combine the Low and High Order Wordint intCombinedkey = (intHighOrderWord << 16) + intLowOrderWord;// The byte order of the result shall be reversed [Example: 0x64CEED7E becomes 7EEDCE64. end example],// and that value shall be hashed as defined by the attribute values.for (int intTemp = 0; intTemp < 4; intTemp++) { generatedKey[intTemp] = Convert.ToByte(((uint)(intCombinedkey & (0x000000FF << (intTemp * 8)))) >> (intTemp * 8)); } }// Implementation Notes List:// --> In this third stage, the reversed byte order legacy hash from the second stage shall be converted to Unicode hex // --> string representation StringBuilder sb = new StringBuilder();for (int intTemp = 0; intTemp < 4; intTemp++) { sb.Append(Convert.ToString(generatedKey[intTemp], 16)); } generatedKey = Encoding.Unicode.GetBytes(sb.ToString().ToUpper());// Implementation Notes List://Word appends the binary form of the salt attribute and not the base64 string representation when hashing// Before calculating the initial hash, you are supposed to prepend (not append) the salt to the keybyte[] tmpArray1 = generatedKey;byte[] tmpArray2 = arrSalt;byte[] tempKey = newbyte[tmpArray1.Length + tmpArray2.Length]; Buffer.BlockCopy(tmpArray2, 0, tempKey, 0, tmpArray2.Length); Buffer.BlockCopy(tmpArray1, 0, tempKey, tmpArray2.Length, tmpArray1.Length); generatedKey = tempKey;// Iterations specifies the number of times the hashing function shall be iteratively run (using each// iteration's result as the input for the next iteration).int iterations = 50000;// Implementation Notes List://Word requires that the initial hash of the password with the salt not be considered in the count.// The initial hash of salt + key is not included in the iteration count. HashAlgorithm sha1 = new SHA1Managed(); generatedKey = sha1.ComputeHash(generatedKey);byte[] iterator = newbyte[4];for (int intTmp = 0; intTmp < iterations; intTmp++) {//When iterating on the hash, you are supposed to append the current iteration number. iterator[0] = Convert.ToByte((intTmp & 0x000000FF) >> 0); iterator[1] = Convert.ToByte((intTmp & 0x0000FF00) >> 8); iterator[2] = Convert.ToByte((intTmp & 0x00FF0000) >> 16); iterator[3] = Convert.ToByte((intTmp & 0xFF000000) >> 24); generatedKey = concatByteArrays(iterator, generatedKey); generatedKey = sha1.ComputeHash(generatedKey); }// Apply the element DocumentProtection documentProtection = new DocumentProtection(); documentProtection.Edit = DocumentProtectionValues.ReadOnly; OnOffValue docProtection = new OnOffValue(true); documentProtection.Enforcement = docProtection; documentProtection.CryptographicAlgorithmClass = CryptAlgorithmClassValues.Hash; documentProtection.CryptographicProviderType = CryptProviderValues.RsaFull; documentProtection.CryptographicAlgorithmType = CryptAlgorithmValues.TypeAny; documentProtection.CryptographicAlgorithmSid = 4; // SHA1// The iteration count is unsigned UInt32Value uintVal = new UInt32Value(); uintVal.Value = (uint)iterations; documentProtection.CryptographicSpinCount = uintVal; documentProtection.Hash = Convert.ToBase64String(generatedKey); documentProtection.Salt = Convert.ToBase64String(arrSalt); wdDocument.MainDocumentPart.DocumentSettingsPart.Settings.AppendChild(documentProtection); wdDocument.MainDocumentPart.DocumentSettingsPart.Settings.Save(); }

 

Now to use this, simply call:

WordprocessingDocument wdDoc = WordprocessingDocument.Open(@"Test_password.docx", true);

ApplyDocumentProtection(wdDoc, "Example");

And next time, when you try to open this document in Word, you will be prompted for the password for editing.

If you would like to know the details on how Microsoft Office applications perform this algorithm, you can refer to following:

[MS-OFFCRYPTO]: Office Document Cryptography Structure Specification
http://msdn.microsoft.com/en-us/library/cc313071.aspx

The element (documentProtection tag) is used to “Protect” a document in Open XML, not to “Secure” a document. Document “Protection” is in plain text XML (in the ZIP package.)  One can open the zip and remove the protected element.

How to implement late bound event handling in case of a VSTO Add-In

$
0
0

If you build a managed component that loads into Office application’s(Word, Excel , PowerPoint) process space, and the managed code uses the PIA (or a custom IA using event delegates) to sink events on the Application object, that application’s instance may encounter problems when used for OLE embedding.

The problems include:

  • Being unable to update object if it is edited twice (in place activated, edited, closed, then activated and edited again) between saves
  • Failure to release lock on object storage, which may inhibit proper closing of the main document file or return an error
  • Crash of Word when one of the above problems occurs and user attempts to edit object again after the reported error

This issue is seen in all current versions of Office (2002, 2003, and 2007).

The problems occur because of the conflicting design behaviours between OLE and .NET over object lifetime. In order to avoid the issue you may try to use the below blog to implement late bound event handling and using IConnectionPointContainer interface.

Excel OLE Embedding Errors if you have Managed Add-In Sinking Application Events in Excel
http://blogs.msdn.com/vsofficedeveloper/pages/excel-ole-embedding-errors-with-managed-addin.aspx

This approach may work well for a COM add-in but in case of a VSTO add-in for Excel you may still encounter an error like-

TargetInvocationException – The interface does not support late bound calls since it does not derive from IDispatch.

The reason for the TargetInvocationException in Excel is that VSTO uses proxy objects (the transparent proxy object always tells Excel that the locale is US English/locale-id 1033 which effectively makes VSTO match VBA behaviour) instead of exposing the application to the actual object model to avoid the Excel locale issue. The Excel locale issue results into following error if you run a VSTO customization for Excel on a machine that has the locale set to something other than US English (locale ID or LCID 1033) –

Exception from HRESULT: 0x800A03EC 

or

Old format or invalid type library.

However introduction of the proxy object causes the  TargetInvocationException in our case because of the inability to typecast the proxy object into Application object.

In order to resolve this error we need to do 2 things -

  1. Set ExcelLocale1033 attribute to false
  2. Mark the assembly as COM Visible

Setting ExcelLocale1033 attribute In the AssemblyInfo.cs file from true to false disables the creation of proxy objects and hence the error which occurred due to typecasting does not occur. Although, this may get you past the above error but you may still encounter an Invalid Case exception.

In order to mark the assmebly as com visible, we can go to AssemblyInfo.cs and set the ComVisible attribute to true.

[assembly: ComVisible(true)]

This is required because the VSTO assemblies are not COM visible by default.

In Word and PowerPoint, you may not face the TargetInvocationException, as there are no proxy objects involved; however, you may get InvalidCastException exception. In that case, you can set the ComVisible attribute for the assembly to true.

One more point to be considered here is that after you set the ExcelLocale1033 to false you are again exposed to the Excel Locale issue. So, if you want to use your add-in across multiple cultures you may want to use one of the workarounds in the article below -

BUG: "Old format or invalid type library" error when automating Excel
http://support.microsoft.com/kb/320369

Using shortcut keys to call a function in an Office Add-in

$
0
0

Recently I came across a problem where a customer was looking for the ways to call a function in an VSTO office add-in using keyboard shortcuts. Since I could not find good documentation on it, I decided to document my findings.

One can implement these in following ways:
1.    Use KeyBindings
2.    Hook the main window and trap the key combinations to launch custom functions.

Using KeyBindings

KeyBindings can be used to assign keyboard shortcuts to macros. Then macros can be used to call the function in managed add-in. I found following are few useful links which talk about the VSTO-VBA integration.

VSTO VBA integration: http://msdn.microsoft.com/en-us/magazine/cc163373.aspx
Calling VSTO code from VBA: http://msdn.microsoft.com/en-us/office/cc178910.aspx
KeyBindings Collection: http://msdn.microsoft.com/en-us/library/bb211991(v=office.12).aspx.

Using Keyboard Hook

This can also be achieved using Windows Hooks(http://msdn.microsoft.com/en-us/library/ms997537.aspx)

Following is a sample class which traps Ctrl+1 keys and writes "A" in A4 cell in Excel (I used this class in a VSTO Excel addin project). This class uses local hook to trap Ctrl+1 keys. It can be used to set hook (by calling its SetHook function), release hook (by calling its ReleaseHook function).

using System;

using System.Diagnostics;

using System.Windows.Forms;

using System.Runtime.InteropServices;

using Microsoft.Office.Core;

using Microsoft.Office.Interop.Excel;

using Excel = Microsoft.Office.Interop.Excel;

 

namespace Hooking

{

public class InterceptKeys

{

public delegate int LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

 

private static LowLevelKeyboardProc_proc = HookCallback;

 

private static IntPtr _hookID = IntPtr.Zero;

 

privates tatic Microsoft.Office.Tools.CustomTaskPane ctpRef = null;

 

//Declare the mouse hook constant. //For other hook types, you can obtain these values from Winuser.h in the Microsoft SDK.

private const int WH_KEYBOARD = 2;

private const int HC_ACTION = 0;

public static void SetHook()

{

_hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());

}

public static void ReleaseHook()

{

UnhookWindowsHookEx(_hookID);

}

private static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)

{

int PreviousStateBit = 31;

bool KeyWasAlreadyPressed = false;

Int64 bitmask = (Int64)Math.Pow(2, (PreviousStateBit - 1));

if (nCode < 0)

{

return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);

}

else

{

if(nCode == HC_ACTION)

{

Keys keyData = (Keys)wParam;

KeyWasAlreadyPressed = ((Int64)lParam & bitmask) > 0;

if (Functions.IsKeyDown(Keys.ControlKey) && keyData == Keys.D1 && KeyWasAlreadyPressed == false)

{

object missing = System.Reflection.Missing.Value;

Excel.

Workbook exBook = Excel_CustomTaskPane_ACC.Globals.ThisAddIn.Application.ActiveWorkbook;

Excel.

Worksheet exSheet = (Excel.Worksheet)exBook.ActiveSheet;

exSheet.get_Range(

"A4", missing).Value2 = "A";

}

}

return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);

}

}

 

[

DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]

 

private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]

[return: MarshalAs(UnmanagedType.Bool)]

 private static extern bool UnhookWindowsHookEx(IntPtrhhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]

private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,IntPtr wParam, IntPtrlParam);

}

public classFunctions

{

public static bool IsKeyDown(Keyskeys)

{

return (GetKeyState((int)keys) & 0x8000) == 0x8000;

}

[DllImport("user32.dll")]

static extern short GetKeyState(intnVirtKey);

}

}

 

How to retain alternate data stream associated with a word document while converting it to newer file format version

$
0
0

Recently I encountered a not so common [;-)] scenario while working with Word 2007:

If you have a Word 97-2003 format document which also has an alternate data stream associated with it, converting this document to the newer file format (DOCX file format) in Word 2007 may result in the loss of the associated alternate data stream (ADS).

After researching this, I found that this behavior is by design of Office 2007 convert operation, and it will become more evident from the following, which elaborates what Word 2007 does when a user selects this option.

When you open a Word 97-2003 file format document in Word 2007, you’ll see a new menu-item “Convert” in the Office Pearl menu, as shown below:

ConvertMenu

On selecting this menu-item, Word will show you a warning saying converting the document to newest format might result in change of the document layout. On selecting OK for that message box, you will assume that the file is now converted to the desired format, but wait that’s not entirely true [:-O].

Instead of immediately converting the document, Word will just mark the current document for conversion to the newest format on the next save/close operation. You can either chose to work with the document or close it immediately. In either of these case, you get a message box saying “Do you want to save the changes”, and clicking “Yes” will actually convert the document before closing it.

Here, conversion of the document to newest format means that Word actually “creates” a new document (DOCX format) and “deletes” the older one. And you might have guessed by now that due to this process, the alternate data stream associated with the original file gets lost.

Now, how do we retain the alternate data stream associated with the older file. Here are some of our options; you can chose either of these based on your scenario:

  1. Repurpose the Convert button implementation using CustomUI XML
  2. Provide a custom button to user for performing this conversion

Option #1

Here, we need to repurpose Office Ribbon command ID “UpgradeDocument”. I will not go into the details of creating a project with custom ribbon XML here; you can refer to following articles to get in-depth documentation for this:

Here is my CustomUI XML for repurposing the Convert button in the Office menu:

<?xmlversion="1.0"encoding="UTF-8"?>
<customUIxmlns="http://schemas.microsoft.com/office/2006/01/customui"onLoad="Ribbon_Load">
<commands>
<commandidMso="UpgradeDocument"onAction="MyConvertHandler"/>
</commands>
</customUI>

Option #2

For implementing this, we can follow the same path of adding a custom UI XML as in Option #1, and instead of adding a <commands> node, we need to add a custom button for user to click on for converting the document.

For more information on how to add custom buttons to ribbon, you can refer to following articles:

You may also say why not just intercept the Convert document event and add a custom logic to retain the ADS (pretty obvious, huh!). Unfortunately there is no such event available in Word for this operation. We can, however, intercept other events like DocumentBeforeSave/DocumentBeforeClose and form a logic for retaining the alternate data stream of original file.

Implementation

OK, now that we have a way to intercept the Convert operation, all we need is the logic to retain the ADS for our file. This we will implement as below:

  1. Read the alternate data stream associated with the original file
  2. Convert the document programmatically
    1. Call Document.Convert() 
    2. Call Document.Save() to force Word perform the conversion.
  3. Create a new alternate data stream and associate it with the new file.
  4. Fill the content of the old stream to the new one.

Please see the attached sample project to see an example of this implementation using Option #1.

Things to try

Excel 2007 and PowerPoint 2007 also exhibit the same behavior as Word 2007, i.e., the associated ADS also get lost when an opened workbook/presentation is converted to a latest version. The good part is, you can implement a similar approach for them too, to retain the ADS.

 

How to efficiently generate OpenXML Workbook from Excel files containing very large amount of data

$
0
0

Few days back, one of my colleague asked me for my help for a requirement to convert an Excel 97-2003 Workbook to an OpenXML based workbook. Although it may sound pretty easy, the catch was that the file to be converted had around millions of rows (you read it right, Millions!). If we go by the standard technique of looping through the cells of the source file, and generate the corresponding XML for the OpenXML file, it would take, if not ages, yet a substantial amount of time to complete.

So, the question was how to efficiently generate the OpenXML from an Excel workbook that contains huge amount of data?

In order to solve this, we decided to use the XSL Transform technique on our source XML’s data to generate the required content for OpenXML. In this post, I am going to show how we achieved this.

Here is the overall flow of what we are going to implement here:

Flow

Before we go into the details of this, let us briefly discuss what is XSL transform. I’ll not go into the details of XSLT, however the overall idea is that using XSLT (eXtensible Stylesheet Language Transformations) file we can transform an XML data to some other format, for e.g. XHTML; and in our case to XML based on a different schema.

For more details and explanation of XSLT, please see following links:

 

Extracting Data From Excel Workbook

Our first task is to extract the data contained in the Excel workbook into an XML file. We will achieve this by using XML mapping in Excel. Here is the snapshot of the workbook that I used for our example:

 ExcelBook

Here is the XSD file that I used to map the data in the above workbook; note that the contents of this file will depend on the structure of the source Excel workbook:

<?xmlversion="1.0"encoding="utf-8"?>
<xs:schemaelementFormDefault="qualified"attributeFormDefault="unqualified"xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:elementname="root">
<xs:complexType>
<xs:sequence>
<xs:elementmaxOccurs="unbounded"name="data">
<xs:complexType>
<xs:sequence>
<xs:elementname="object"type="xs:string"/>
<xs:elementname="price"type="xs:integer"/>
<xs:elementname="count"type="xs:short"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

After mapping this schema, we can extract the data from Excel by saving the current file as XML Data (*.xml) format. Here the XML that was generated from my example workbook using the above schema; let’s call it data.xml:

<root>
<data>
<object>Table</object>
<price>150</price>
<count>1</count>
</data>
...
</root>

After generating our data XML, next task is to prepare XSLT for transforming this data XML to our required format.

 

 

Preparing XSLT

In my XSLT, I have two templates that I apply on the generated Data XML, one at the root node, and the other for each data element and its children.

For each data node, we need to generate a row element as per Spreadsheet ML, and for each child element of data node we need to generate col elements. Each row element that we need to generate, must have an attribute named r which will contain the row number. Similarly for each col node we need to generate an attribute named r that needs to have value of the cell name which this column corresponds to.

For e.g. for first row in our data.xml our transformed row element should have an attribute r=”1”, and its first col element should have an attribute r=”A1”, second col element should have r=”B1” and so on.

Now that actual data value that is contained in a cell, can be maintained in following two ways according to Spreadsheet ML specifications:

  1. Keep each value in a separate file called sharedStrings.xml in the OpenXML package
  2. Keep them as inline strings as part of the sheet.xml

We will be using the second way to generate our XML. The second way also requires that each col element specifies the type attribute (t) with value as inlineStr. This tells Excel that it contains an inline string and Excel doesn’t need to search for the value from sharedStrings.xml. The actual content of the inline string is contained in the child element named is under the col node.

Here is the XSLT that I used for transforming our data XML to the required XML:

<?xmlversion="1.0"encoding="utf-8"?>
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"exclude-result-prefixes="msxsl">
<xsl:outputmethod="xml"indent="yes"/>



<xsl:templatematch="root">
<xsl:elementname="sheetData">
<xsl:apply-templatesselect="data"></xsl:apply-templates>
</xsl:element>
</xsl:template>



<xsl:templatematch="data">
<xsl:variablename="rowID">
<xsl:numbervalue="position()"format="1"/>
</xsl:variable>
<xsl:elementname="row">
<xsl:attributename="r">
<xsl:value-ofselect="$rowID"/>
</xsl:attribute>



<xsl:for-eachselect="*">
<xsl:elementname="c">
<xsl:variablename="colID">
<xsl:numbervalue="position()"format="A"/>
</xsl:variable>
<xsl:attributename="r">
<xsl:value-ofselect="concat(string($colID),string($rowID))"/>
</xsl:attribute>
<xsl:attributename="t">
<xsl:text>inlineStr</xsl:text>
</xsl:attribute>
<xsl:elementname="is">
<xsl:elementname="t">
<xsl:value-ofselect="."/>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:for-each>

</xsl:element>
</xsl:template>

</xsl:stylesheet>


 

Generating transformed XML using the XSLT

Now that we have our XSLT ready, we need to transform our data XML into our desired format using this XLST. For this example, I used a C# based Windows Forms application to perform this task.

Here is the code snippet I used:

XPathDocument xDoc = new XPathDocument("data.xml");
XslCompiledTransform xXslt = new XslCompiledTransform();
xXslt.Load("test.xslt");

XmlTextWriter xOutput = new XmlTextWriter("output.xml", null);

xXslt.Transform(xDoc, null, xOutput);

xOutput.Close();

 

Running this snippet, results in an XML in following format:

<?xmlversion="1.0"encoding="utf-8"?>
<sheetData>
<rowr="1">
<cr="A1"t="inlineStr">
<is>
<t>Table</t>
</is>
</c>
<cr="B1"t="inlineStr">
<is>
<t>150</t>
</is>
</c>
<cr="C1"t="inlineStr">
<is>
<t>1</t>
</is>
</c>
</row>
...
...
</sheetData>

 

Generating XLSX from our transformed XML

This involves following steps:

  1. Generate a blank XLSX file in memory. I have used OpenXML 2.0 SDK for this task, and also referred to this post:

         How To generate a .xlsx using Open XML SDK without loading any .xml
  2. Insert the sheetData element from our transformed XML into the sheet.xml

Here is the code snippet I used:

// Read the generated XML
StreamReader sr = File.OpenText("output.xml");
string strSheetData = sr.ReadToEnd();

using (SpreadsheetDocument doc = SpreadsheetDocument.Create("output.xlsx", DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbook = doc.AddWorkbookPart();
WorksheetPart sheet = workbook.AddNewPart<WorksheetPart>();
string sheetId = workbook.GetIdOfPart(sheet);

// Create a blank XLSX file
string XML = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><workbook xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""><sheets><sheet name=""{1}"" sheetId=""1"" r:id=""{0}"" /></sheets></workbook>";
XML = string.Format(XML, sheetId, "Sheet1");
this.AddPartXml(workbook, XML);

// Insert our sheetData element to the sheet1.xml
XML = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><worksheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" >{0}</worksheet>";
XML = string.Format(XML, strSheetData);
this.AddPartXml(sheet, XML);

doc.Close();
}


 

 

protectedvoid AddPartXml(OpenXmlPart part, string xml)
{
using (Stream stream = part.GetStream())
{
byte[] buffer = (new UTF8Encoding()).GetBytes(xml);
stream.Write(buffer, 0, buffer.Length);
}
}


 

After running this code snippet, a file named output.xlsx is generated with our transformed XML inserted into its sheet.xml. Here is how it looked on my machine, when opened in Excel 2007:

Generated XLSX

 

As an example, I have just inserted my data in the sheet.xml. In a more realistic scenario, you might want to implement extra tasks like:

  • Pull data from a database as XML (instead of extracting it from an XLS file) and transform that to the required XML using XSLT.
  • Add header row to the sheet.xml in addition to the data
  • Define styles for the columns. You might have to modify your XSLT to generate columns with associated styles.
  • Format columns for different types of data.

You can also play around with XSLT to generate a full sheet.xml for your requirement, rather than generating just the sheetData element of sheet.xml.

With these I am ending this post. Please feel free to post your queries as comments, I will definitely try to answer them.


Smart tags built using Visual Basic 6 don't work with Word 2010 and PowerPoint 2010.

$
0
0

 

Recently I worked on a problem where the Visual Basic 6 (VB6) Smart tags were not working with Word 2010 and PowerPoint 2010. The same VB6 smart tags work fine in Word 2007 and PowerPoint 2007.

The issue was, when we right click a term (which the smart tag recognizes) and select the "additional actions" option on the context menu, the office application (Word 2010 or PowerPoint 2010) crashes, as shown in the screenshot below:

Figure1

Exceptionally, VB6 Smart tags work fine with Excel 2010.

On analyzing the code for Word 2010 and PowerPoint 2010, I found that the problem is because of the change in the way smart tag related callbacks are handled in Word & PowerPoint 2010. In Office 2007 and earlier versions, smart tags related callbacks used to happen on the same thread. But in Office 2010 (Word & PowerPoint), the smart-tags recognize callbacks happen on different thread than the smart-tag initialization callbacks. Visual Basic 6 supports running VB code on threads where VB6 is initialized. It doesn't work if the Thread local storage (TLS) (http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx) is not fully initialized.

So if we can ensure that VB is initialized on the thread (thread 0) where the recognize is called, this issue won't come up.

As a workaround, one can create a do-nothing/dummy VB6 COM add-in for Word 2010 and PowerPoint 2010 which initializes the thread 0 on which recognize is called later. It resolves the issue. Here is an article that you can refer to for this:

Building a COM Add-in using VB6: http://msdn.microsoft.com/en-us/library/aa140195(office.10).aspx

VSTO 4.0 SP1 will cause a VSTO Addin to not find its config file.

$
0
0

Few days back i worked on a problem where a VSTO addin was failing to load after VSTO 4.0 SP1 was installed on the machine. This addin was reading some data from its application configuration file. There was some error during the load of the addin and its load behavior changed to 2 without any error message. During my research, I found that issue is caused by a subtle interaction between the add-in loading enhancements we made in SP1 and app domain configuration behavior in the .NET Framework.

This design requirement was introduced with the new Fast Path add-in loading functionality in the SP1 version of the VSTO Runtime (http://blogs.msdn.com/b/vsto/archive/2010/11/30/performance-improvements-coming-soon-to-a-service-pack-near-you-stephen-peters.aspx). When an add-in is loading via Fast Path and it relies on a *.dll.config file, the manifest path provided for the add-in must be a complete, well-formed URI, including a URI scheme. This new requirement is related to the refined security process for Fast Path loading, and helps ensure that only trusted files are loaded. So for example, if your addin's manifest string value in the registry has following value:

Manifest="C:\Program Files\<Path>\WordAddIn.vsto|vstolocal" 

The addin will fail to load.

To make the addin work, change the value to:

Manifest="file:///C:/Program Files/<Path>/WordAddIn.vsto|vstolocal

Excel xml file behaves differently when opened in 32 bit and 64 bit versions of Internet Explorer

$
0
0

Issue:

Suppose you have an Excel XML file and you try to open it in inside an Internet Explorer 32 bit version(by drag drop), it opens just fine in Excel on a Microsoft Office 2010 machine. But if you try to open the same XML file inside a 64 bit version of Internet explorer, you will find the xml text being displayed inside the browser instead of the Excel file being launched.

 

Cause:

iexplore.exe attempts to enumerate both Content Type- Text/XML and Content Type-application/vnd-ms-excel, however, in the failing x64 bit iexplore.exe it only enumerates Content Type-Text/XML but Content Type-application/vnd-ms-excel is NOT found in registry.

 

Resolution :

Adding the following registry entry  resolves the issue and the Excel xml file opens fine even in the 64 bit Internet Explorer–

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Common\Filter\text/xml]

"Excel.Sheet"="application/vnd.ms-excel"

Excel RTD Caching–Memory usage

$
0
0

 

Microsoft Office Excel provides a worksheet function, RealTimeData (RTD). This function enables you to call a Component Object Model (COM) Automation server  provides you with a way to view and update data in real time. This real-time data (RTD) feature is great for working with constantly-changing data such as stock quotes, currency exchange rates, inventory levels, price quotes, weather information, sports scores, and so on.

To achieve high performance in such time critical scenarios, Excel has chosen to cache RTD topics related data structures at the application level (viz. they will be cached till the application lifetime). Though this offers the performance which is needed for real time scenarios, but as you can imagine, this does have an impact on the Excel memory usage in some scenarios, for example

1)  When you are using Excel RTD with dynamic “topics”

2) You may encounter same thing, if you happen to open multiple workbooks with large number unique RTD topics

In both of these scenarios, if you happen to consume total addressable heap memory for that particular architecture, you may get error like “Out of virtual memory” or “There is not enough memory to complete the operation” etc. or even a crash.

If you are running into any of the above scenario, the way to move forward is to close excel instance when there is a significant memory pressure.

More about RTD Servers:

Building a Real-Time Data Server in Excel 2002
How to set up and use the RTD function in Excel

Word 2007 Bug: Accessing content controls having ID more than 2^31 results in an error.

$
0
0

Few days back I worked on an issue where a Word 2007 VSTO document solution, developed using VSTO 3.0, was behaving erratically when VSTO 4.0 was installed on the same machine. The Word document was very simple, it just had a Rich Text content control on it and the customer was using the following code to manipulate it:

   1:  Private Sub ThisDocument_Open() Handles Me.Open
   2:          Me.TrackFormatting = False
   3:          Me.TrackRevisions = False
   4:          rt1.LockContentControl = False
   5:          rt1.LockContents = False
   6:          rt1.Text = "TEST COMPLETED"
   7:          Me.TrackFormatting = True
   8:          Me.TrackRevisions = True
   9:          rt1.LockContentControl = True
  10:          rt1.LockContents = True
  11:  End Sub

He was getting the following error on the line where he was accessing the rich text control in the code (Line number 4 in the above code snippet):

image

Surprisingly, on uninstalling the VSTO 4.0 from the machine the solution worked perfectly fine. Also, the solution worked without any problem if I remove the content control and add it again to the document.

After spending hours on troubleshooting the problem, we discovered that the problem was due to a bug in Word 2007. The bug was, if a content control’s id is greater than 2^31 (2 to the power 31) and you try to access it like the following (VBA code):

ActiveDocument.ContentControls.Item ("3678349844").ID

Word will throw the following error:

image

However, if you try to access the same content control using the following:

ActiveDocument.ContentControls (INDEX).ID

It works, as shown below:

image

 

The VSTO 3.0 uses the second method to access the content controls on the document. It loops through the content controls collection and matches the ID of the control it is looking for.

In case of VSTO 4.0, the content control is retrieved using “ContentControls.Item (ID)”, where ID is the ID of the content control it is looking for and hence we see the problem.

The problem with the VSTO document was that the ID of the content control it had was 3678349844, which is greater than 2^31 (2147483648).

While the Content Control is being added to the document, the ID is being generated as unsigned DWORD, that is it can have any value up to 2^32, however, while trying to get/retrieve the control, the passed in ID is being converted to signed DWORD which results in an overflow and hence the error.

This bug is fixed in Word 2010 but it still exists with Word 2007.

One of the workaround to the problem is to delete the content controls (which have ID more than 2^31) and add them again (making sure that the ID is less than 2^31). Another workaround could be to access the underlying inner object (Word RichTextControl) rather than using the VSTO wrapped one. For example:

 

Imports Word = Microsoft.Office.Interop.Word
 
Dim cc As Word.ContentControl
 
For Each cc In Me.ContentControls
    If (cc.Tag.Equals("MyContentControl")) Then
        cc.LockContentControl = False
        cc.LockContents = False
        cc.Range.Text = "Hello World!!!"
    End If
Next
Viewing all 60 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>