Changing Footnote numbering fails for some word documents with Interop - c#

I have the following code being used with Word 2016 installed, referencing Microsoft Word 16.0 Object Library:
private void RefreshFootnoteNumbering(FileManagement.FileManager FileManager)
{
Console.WriteLine(DateTime.Now.ToString() + " Refreshing footnotes DOCX");
// Opening and saving in word generates the required element
var Word = GetWordApp();
try
{
var DocxPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.ChangeExtension(FileManager.HtmlFileLocation, "docx"));
Console.WriteLine(DateTime.Now.ToString() + "\tOpening document");
var Doc = GetWordDoc(Word, DocxPath);
try
{
// Fails on these lines below (both cause the same exception)
Doc.Footnotes.NumberingRule = InteropWord.WdNumberingRule.wdRestartPage;
Doc.Footnotes.Location = InteropWord.WdFootnoteLocation.wdBottomOfPage;
Doc.SaveAs2(DocxPath, InteropWord.WdSaveFormat.wdFormatXMLDocument, AddToRecentFiles: false, EmbedTrueTypeFonts: true);
}
finally
{
Doc.Close();
Doc = null;
}
}
finally
{
Word.Quit();
Word = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
This works for most documents, however for some I get the following exception:
System.Runtime.InteropServices.COMException was unhandled
ErrorCode=-2146823680
HResult=-2146823680
HelpLink=wdmain11.chm#37376
Message=Value out of range
Source=Microsoft Word
StackTrace:
at Microsoft.Office.Interop.Word.Footnotes.set_NumberingRule(WdNumberingRule prop)
Other interop functions (iterating/manipulating fields, sections etc) work fine, it seems to be just altering footnotes in this way that have an issue. Altering them from within Word itself works fine.
Has anyone encountered this issue before? Any work arounds or alternatives?
I've tried recording a macro, and it gave this VBA code:
With ActiveDocument.Range(Start:=ActiveDocument.Content.Start, End:= _
ActiveDocument.Content.End).FootnoteOptions
.Location = wdBottomOfPage
.NumberingRule = wdRestartContinuous
.StartingNumber = 1
.NumberStyle = wdNoteNumberStyleArabic
.NumberingRule = wdRestartPage
.LayoutColumns = 0
End With
If I run this macro, I get the same error (value out of range, error number 4608) on the .Location line, whether I run from the debugger, or just view macros -> run.
I've also tried to translate that VBA into C# code:
var Options = Doc.Range(Doc.Content.Start, Doc.Content.End).FootnoteOptions;
Options.Location = InteropWord.WdFootnoteLocation.wdBottomOfPage;
Options.NumberingRule = InteropWord.WdNumberingRule.wdRestartPage;
However, this gives the same error.

Still not sure of the exact cause (possibly something further up in my code creating different sections); still not clear on why it worked when word recorded the macro, but not when running it.
Anyway, I managed to alter the C# code to the below, which seems to do the job and actually works!
foreach(InteropWord.Footnote FootNote in Doc.Footnotes)
{
FootNote.Reference.FootnoteOptions.NumberingRule = InteropWord.WdNumberingRule.wdRestartPage;
FootNote.Reference.FootnoteOptions.Location = InteropWord.WdFootnoteLocation.wdBottomOfPage;
}

Related

Office DocumentProperty returning dynamic instead of DocumentProperties-Collection

I am trying to access and modify DocumentProperties in Office (I try Word atm, but later on I want to expand to Excel, which shouldn't be a problem since the interop works quite similar), but at the moment I have the very concerning problem of not getting the type I would guess.
Here is a part of my code:
var testWordApp = new Word.Application();
var testWordFile = testWordApp.Documents.Open(
#"C:\Work\Intern\DocPropChanger_Projektarbeit\" +
#"PrototypeVorlagen\Proj-Nr_QPP_VersionVorlage_endeu.docx",
ReadOnly: false, Visible: false);
dynamic test = testWordFile.BuiltInDocumentProperties;
This code does give me the builtin DocumentProperties like last author, revision number and so on and I can go through it with an foreach, but it is different from what it should be.
MSDN and other sources clearly cast the returned object into an collection of DocumentProperties whereas if I do so aswell get an InvalidCastException.
I am currently working with VS 2015 Express and Office 13, but I already tried VSTO in VS 2015 Community with the same result.
https://msdn.microsoft.com/en-us/library/dhxe2d75.aspx
Here is a question by an other user in SO, who does (more or less)the same thing:
Accessing Excel Custom Document Properties programatically
It does seem to work for him, I have references to the proper parts of the framework, those are:
Office.Core
Office.Interop.Word
The main problem that results out of this inconvenience of having to use
dynamic
result in not being able to add my own Properties, which I try like that:
testWordFile.CustomDocumentProperties.Add(
Name: d.Name,
LinkToContent: false,
Type: 4,
Value: "Testtext aus Programm");
€: I also tried adding to test which turned out the same way.
This results in an exception:
HRESULT: 0x8000FFFF
This is after a short look in Google a pretty generic error.
What can I do to get the correct collection back? And am I doing a mistake while adding the property?
I looked among others (one of which is the linked above MSDN-page) at this sites for reference:
https://stackoverflow.com/a/12690798/3664953
€²:
For clearification:
I have to get every custom property that is set, even without knowing the name, so I didn't really find an approach other than to use the previously given approach in using dynamic and working with that.
As asked by Cindy Meister, I am currently NOT using VSTO but, also as previously stated, I already tried an approach with that, resulting in the same problems I am running into now, which can be linked in my inexperience with VSTO ...
Here is a more complete code from my class, just for the sake of it:
This is a prototype so all variables used are not named in a clearly understandable way, which shouldn't be a big problem, since the code isn't too complex atm.
var testWordApp = new Word.Application();
var testWordFile = testWordApp.Documents.Open(
#"C:\Work\Intern\DocPropChanger_Projektarbeit"+
#"\PrototypeVorlagen\Proj-Nr_QPP_VersionVorlage_endeu.docx",
ReadOnly: false, Visible: false);
dynamic test = testWordFile.BuiltInDocumentProperties;
Console.WriteLine(test.GetType());
foreach (dynamic d in test)
{
//TryCatch due to the fact, that I also get some more stuff, that are not Properties...
try
{
//I wanted to check the returned Types and if they have one at all
//This was something someone in the internet stated
//(Props not having a valid Type ...)
Console.WriteLine("\r\n---------\r\n");
Console.WriteLine(d.GetType());
Console.WriteLine(d.Name + " # " + d.Name.GetType());
Console.WriteLine(d.Type + " # " + d.Type.GetType());
Console.WriteLine(d.Value + " # " + d.Value.GetType());
}
catch
{ }
}
dynamic test2 = testWordFile.CustomDocumentProperties;
Console.WriteLine(test2.GetType());
foreach (dynamic d in test2)
{
try
{
Console.WriteLine("\r\n---------\r\n");
Console.WriteLine(d.GetType());
Console.WriteLine(d.Name + " # " + d.Name.GetType());
Console.WriteLine(d.Type + " # " + d.Type.GetType());
Console.WriteLine(d.Value + " # " + d.Value.GetType());
if(d.Name == "TestpropText")
{
//For highlighting
Console.WriteLine("#+#+#+#+#+#+#+#+#+#+#");
//This works like a charm
testWordFile.CustomDocumentProperties[d.Name].Delete();
//This results in the previously mentioned HRESULT: 0x8000FFFF
test.Add(Name: d.Name, LinkToContent: false, Type: 4, Value: "Testtext aus Programm");
}
}
catch(Exception e)
{
Console.WriteLine(e.InnerException);
}
}
testWordApp.Documents.Save(NoPrompt: true, OriginalFormat: true);
testWordApp.Application.Quit(SaveChanges: false, OriginalFormat: false,
RouteDocument: false);

How to generate new PDF using PDFLib 9 in C#?

I am trying to create a new, blank PDF document using PDFLib 9 in my .Net project.
I've looked at some of the tutorials and documentation but could not get it working.
Here is the code I have in a unit test:
public void Test()
{
try
{
var outfile = "newPDF.pdf";
const string docOption = "searchpath={C:\\Users\\me\\Desktop\\Test_Pdfs}";
var p = new PDFlib();
p.set_option(docOption);
p.set_option("errorpolicy=return");
var x = p.begin_document(outfile, "");
if (x != -1)
{
p.begin_page_ext(595.0, 842.0, "topdown");
p.end_page_ext("");
p.end_document("");
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
}
I don't get any errors in the catch, and the test does not fail.
However, when I enable CLR exceptions I get a number of CommunicationAbortedException's and InvalidOperationExceptions that basically talk about how some connection was closed.
This all happens after the last bracket. Also, the PDF is simply not created.
Any insight is really appreciated!
looks like something wrong with searchpath.
I commented out your docOption and PDF is created. this should give you some clue.

When using MergeField FieldCodes in OpenXml SDK in C# why do field codes disappear or fragment?

I have been working successfully with the C# OpenXml SDK (Unofficial Microsoft Package 2.5 from NuGet) for some time now, but have recently noticed that the following line of code returns different results depending on what mood Microsoft Word appears to be in when the file gets saved:
var fields = document.Descendants<FieldCode>();
From what I can tell, when creating the document in the first place (using Word 2013 on Windows 8.1) if you use the Insert->QuickParts->Field and choose MergeField from the Field names left hand pane, and then provide a Field name in the field properties and click OK then the field code is correctly saved in the document as I would expect.
Then when using the aforementioned line of code I will receive a field code count of 1 field. If I subsequently edit this document (and even leave this field well alone) the subsequent saving could mean that this field code no longer is returned in my query.
Another case of the same curiousness is when I see the FieldCode nodes split across multiple items. So rather than seeing say:
" MERGEFIELD Author \\* MERGEFORMAT "
As the node name, I will see:
" MERGEFIELD Aut"
"hor \\* MERGEFORMAT"
Split as two FieldCode node values. I have no idea why this would be the case, but it certainly makes my ability to match nodes that much more exciting. Is this expected behaviour? A known bug? I don't really want to have to crack open the raw xml and edit this document to work until I understand what is going on. Many thanks all.
I came across this very problem myself, and found a solution that exists within OpenXML: a utility class called MarkupSimplifier which is part of the PowerTools for Open XML project. Using this class solved all the problems I was having that you describe.
The full article is located here.
Here are some pertinent exercepts :
Perhaps the most useful simplification that this performs is to merge adjacent runs with identical formatting.
It goes on to say:
Open XML applications, including Word, can arbitrarily split runs as necessary. If you, for instance, add a comment to a document, runs will be split at the location of the start and end of the comment. After MarkupSimplifier removes comments, it can merge runs, resulting in simpler markup.
An example of the utility class in use is:
SimplifyMarkupSettings settings = new SimplifyMarkupSettings
{
RemoveComments = true,
RemoveContentControls = true,
RemoveEndAndFootNotes = true,
RemoveFieldCodes = false,
RemoveLastRenderedPageBreak = true,
RemovePermissions = true,
RemoveProof = true,
RemoveRsidInfo = true,
RemoveSmartTags = true,
RemoveSoftHyphens = true,
ReplaceTabsWithSpaces = true,
};
MarkupSimplifier.SimplifyMarkup(wordDoc, settings);
I have used this many times with Word 2010 documents using VS2015 .Net Framework 4.5.2 and it has made my life much, much easier.
Update:
I have revisited this code and have found it clears upon runs on MERGEFIELDS but not IF FIELDS that reference mergefields e.g.
{if {MERGEFIELD When39} = "Y???" "Y" "N" }
I have no idea why this might be so, and examination of the underlying XML offers no hints.
Word will often split text runs with into multiple text runs for no reason I've ever understood. When searching, comparing, tidying etc. We preprocess the body with method which combines multiple runs into a single text run.
/// <summary>
/// Combines the identical runs.
/// </summary>
/// <param name="body">The body.</param>
public static void CombineIdenticalRuns(W.Body body)
{
List<W.Run> runsToRemove = new List<W.Run>();
foreach (W.Paragraph para in body.Descendants<W.Paragraph>())
{
List<W.Run> runs = para.Elements<W.Run>().ToList();
for (int i = runs.Count - 2; i >= 0; i--)
{
W.Text text1 = runs[i].GetFirstChild<W.Text>();
W.Text text2 = runs[i + 1].GetFirstChild<W.Text>();
if (text1 != null && text2 != null)
{
string rPr1 = "";
string rPr2 = "";
if (runs[i].RunProperties != null) rPr1 = runs[i].RunProperties.OuterXml;
if (runs[i + 1].RunProperties != null) rPr2 = runs[i + 1].RunProperties.OuterXml;
if (rPr1 == rPr2)
{
text1.Text += text2.Text;
runsToRemove.Add(runs[i + 1]);
}
}
}
}
foreach (W.Run run in runsToRemove)
{
run.Remove();
}
}
I tried to simplify the document with Powertools but the result was a corrupted word file. I make this routine for simplify only fieldcodes that has specifics names, works in all parts on the docs (maindocumentpart, headers and footers):
internal static void SimplifyFieldCodes(WordprocessingDocument document)
{
var masks = new string[] { Constants.VAR_MASK, Constants.INP_MASK, Constants.TBL_MASK, Constants.IMG_MASK, Constants.GRF_MASK };
SimplifyFieldCodesInElement(document.MainDocumentPart.RootElement, masks);
foreach (var headerPart in document.MainDocumentPart.HeaderParts)
{
SimplifyFieldCodesInElement(headerPart.Header, masks);
}
foreach (var footerPart in document.MainDocumentPart.FooterParts)
{
SimplifyFieldCodesInElement(footerPart.Footer, masks);
}
}
internal static void SimplifyFieldCodesInElement(OpenXmlElement element, string[] regexpMasks)
{
foreach (var run in element.Descendants<Run>()
.Select(item => (Run)item)
.ToList())
{
var fieldChar = run.Descendants<FieldChar>().FirstOrDefault();
if (fieldChar != null && fieldChar.FieldCharType == FieldCharValues.Begin)
{
string fieldContent = "";
List<Run> runsInFieldCode = new List<Run>();
var currentRun = run.NextSibling();
while ((currentRun is Run) && currentRun.Descendants<FieldCode>().FirstOrDefault() != null)
{
var currentRunFieldCode = currentRun.Descendants<FieldCode>().FirstOrDefault();
fieldContent += currentRunFieldCode.InnerText;
runsInFieldCode.Add((Run)currentRun);
currentRun = currentRun.NextSibling();
}
// If there is more than one Run for the FieldCode, and is one we must change, set the complete text in the first Run and remove the rest
if (runsInFieldCode.Count > 1)
{
// Check fielcode to know it's one that we must simplify (for not to change TOC, PAGEREF, etc.)
bool applyTransform = false;
foreach (string regexpMask in regexpMasks)
{
Regex regex = new Regex(regexpMask);
Match match = regex.Match(fieldContent);
if (match.Success)
{
applyTransform = true;
break;
}
}
if (applyTransform)
{
var currentRunFieldCode = runsInFieldCode[0].Descendants<FieldCode>().FirstOrDefault();
currentRunFieldCode.Text = fieldContent;
runsInFieldCode.RemoveAt(0);
foreach (Run runToRemove in runsInFieldCode)
{
runToRemove.Remove();
}
}
}
}
}
}
Hope this helps!!!

How to access WinRM in C#

I'd like to create a small application that can collect system information (Win32_blablabla) using WinRM as opposed to WMI. How can i do that from C#?
The main goal is to use WS-Man (WinRm) as opposed to DCOM (WMI).
I guess the easiest way would be to use WSMAN automation. Reference wsmauto.dll from windwos\system32 in your project:
then, code below should work for you. API description is here: msdn: WinRM C++ API
IWSMan wsman = new WSManClass();
IWSManConnectionOptions options = (IWSManConnectionOptions)wsman.CreateConnectionOptions();
if (options != null)
{
try
{
// options.UserName = ???;
// options.Password = ???;
IWSManSession session = (IWSManSession)wsman.CreateSession("http://<your_server_name>/wsman", 0, options);
if (session != null)
{
try
{
// retrieve the Win32_Service xml representation
var reply = session.Get("http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service?Name=winmgmt", 0);
// parse xml and dump service name and description
var doc = new XmlDocument();
doc.LoadXml(reply);
foreach (var elementName in new string[] { "p:Caption", "p:Description" })
{
var node = doc.GetElementsByTagName(elementName)[0];
if (node != null) Console.WriteLine(node.InnerText);
}
}
finally
{
Marshal.ReleaseComObject(session);
}
}
}
finally
{
Marshal.ReleaseComObject(options);
}
}
hope this helps, regards
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Hope this helps, I've been using this for a while with my automated deployments. Please leave comments if you find issues.
I would like to note that this shows an interop error by default in Visual Studio 2010.
c.f. http://blogs.msdn.com/b/mshneer/archive/2009/12/07/interop-type-xxx-cannot-be-embedded-use-the-applicable-interface-instead.aspx
There appear to be two ways to solve this. This first is documented in the article listed above and appears to be the correct way to handle the problem. The pertinent changes for this example is:
WSMan wsManObject = new WSMan();
This is in lieu of IWSMan wsman = new WSManClass(); which will throw the error.
The second resolution is to go to the VS2010—>Solution Explorer—>Solution—>Project—>References and select WSManAutomation. Right click or hit Alt-Enter to access the properties. Change the value of the "Embed Interop Types" property of the wsmauto reference.

ImportXmlWithProgress not updating result attribute of importjob

I tried to write some code to import a large customization containing 50+ entities. I used the microsoft article 'ImportXmlWithProgress Message (CrmService) as a bases, but was not getting the output I expected.
The 'job.data' in the following code was not changing from the original parameterxml data. So this implies to me that the import was not sucessful. I imported the same compressed importexportxml using the microsoft web ui, and it worked fine. So I'm wondering why my job.data is not being updated with 'result' attributes for each entity that is imported.
Below is my method to import.
private void ImportEntitySchema()
{
const string parameterXml = #"<importexportxml>
<entities>
{0}
</entities>
<nodes/>
<securityroles/>
<settings/>
<workflows/>
</importexportxml>";
var importRequest = new ImportCompressedXmlWithProgressRequest
{
ImportJobId = Guid.NewGuid(),
CompressedCustomizationXml = GetCompressedCustomizationXmlFromEmbeddedResource(),
ParameterXml = string.Format(parameterXml, string.Join("\n", _entitySchemaEntityNames.Select(item => string.Format("<entity>{0}</entity>", item)).ToArray()))
};
try
{
_crmService.Execute(importRequest);
}
catch (Exception e)
{
//Error resulted from import request
}
// Retrieve the results of the import.
XmlNode node;
do
{
Thread.Sleep(2000);
var job = (importjob)_crmService.Retrieve(EntityName.importjob.ToString(), importRequest.ImportJobId, new AllColumns());
var data = new XmlDocument();
data.LoadXml(job.data);
node = data.SelectSingleNode("importexportxml/entities/entity/#result");
} while (node == null);
//code below here never gets executed because the loop continues infinitely
}
I've been looking, but haven't found any/many [useful] examples on the net of the ImportXmlWithProgress being used. Hopefully someone has used it and has an idea of how to get it working.
I remember having trouble with this message, I just can't remember exactly what the trouble was. How big is your import file? We also brewed an import utility for importing our customizations and I use the ImportCompressedAllXmlRequest synchronously (no timeout) on a BackgroundWorker thread. For large amounts of customizations you may have to look at: http://support.microsoft.com/kb/918609. We typically split up our customizations into a bunch of small imports to avoid this.
Should the XPath be "importexportxml/entities/entity[#result]"?

Categories