Modify the Outlook CustomUI XML by powershell - powershell

friends. I want to check every of my company users' outlook customUI configuration file. Delete 2 UI buttons if any. I am trying to do it by powershell. But failed to do so. Any advice for my script?
Below is the XML content of olkexpplorer.officeUI. I need to check & delete the items in blockquotes in case they are existed in the configuration file.
<mso:customUI xmlns:x1="Microsoft.Forefront.SpamReporterAddin.Connect" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui">
<mso:ribbon>
<mso:qat />
<mso:tabs>
<mso:tab idQ="mso:TabMail">
<mso:group id="mso_c1.45620CF" label="Phishing Report" imageMso="TrustCenter" autoScale="true"
<mso:control idQ="x1:ExplorerPhishReportMenuButton" imageMso="TrustCenter" visible="true"/>
</mso:group>
> <mso:group id="mso_c2.14817EBA" label="Junk" autoScale="true">
> <mso:control idQ="x1:ExplorerSpamReportMenuButton" visible="true" />
> <mso:control idQ="x1:ExplorerPhishReportMenuButton" imageMso="GreenBall" visible="true" />
> </mso:group>
</mso:tab>
</mso:tabs>
</mso:ribbon>
</mso:customUI>
Here is my script
$input = [xml](Get-Content -Path “$path_to_office\olkexplorer.officeUI”)
$deletenames="mso_c2.14817EBA"
($input.customUI.ChildNodes |Where-object { $deletenames -contains $_.Name}) | ForEach-
Object{[void]$_.ParentNode.RemoveChild($_)}
$input.save(“$path_to_office\new.officeUI”)

First of all, it is not clear how the ribbon XML looks like after running your code:
$input.save(“$path_to_office\new.officeUI”)
Anyway, you can do any modifications until the ribbon XML is loaded by the host application. At runtime, you may consider using callbacks where they are available and call the IRIbbonUI.Invalidate or IRibbonUI.InvalidateControl methods to get your custom UI invalidated and callbacks triggered. Here is what MSDN states:
For each of the callbacks the add-in implements, the responses are cached. For example, if an add-in writer implements the getImage callback procedure for a button, the function is called once, the image loads, and then if the image needs to be updated, the cached image is used instead of recalling the procedure. This process remains in-place until the add-in signals that the cached values are invalid by using the Invalidate method, at which time, the callback procedure is again called and the return response is cached. The add-in can then force an immediate update of the UI by calling the Refresh method.

The problem with your approach is that ChildNodes() only returns the immediate child nodes and not all child nodes (and using $input as a variable name is not considered a good practice anyway because its a reserved variable name).
So instead of ChildNodes() I would access the child node directly like so
$input.customUI.ribbon.tabs.tab.group | where-object { $DeleteNames -contains $_.id}
I would prefer the System.Xml.Linq classes like XDocument and XElement over the [xml] type alias because they make dealing with Xml inside a PowerShell script a little more convenient.
if this is the input xml:
$XmlCode = #'
<mso:customUI xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui">
<mso:ribbon>
<mso:qat><mso:sharedControls>
<mso:control idQ="mso:FileNewDefault" visible="false"/>
<mso:control idQ="mso:FileOpen" visible="false"/>
<mso:control idQ="mso:FileSave" visible="true"/>
<mso:control idQ="mso:FileSendAsAttachment" visible="false"/>
<mso:control idQ="mso:FilePrintQuick" visible="false"/>
<mso:control idQ="mso:SpellingAndGrammar" visible="false"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:Undo" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:RedoOrRepeat" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:TableDrawTable" visible="false"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:FileOpenRecentFile" visible="false"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:FontDialog" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:FontSizeDecreaseWord" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:FontSize" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:PageSetupDialog" visible="true"
insertBeforeQ="mso:PrintPreviewAndPrint"/>
<mso:control idQ="mso:PrintPreviewAndPrint" visible="true"/>
</mso:sharedControls>
</mso:qat>
</mso:ribbon>
</mso:customUI>
'#
the following Powershell commands would remove all mso:control nodes with visible="false":
using namespace System.Xml.Linq
Add-Type -Assembly System.Xml.Linq
$xmlCode = "<<as shown above>>"
$xRoot = [XDocument]::Parse($xmlCode)
$msoNs = [XNamespace]::get("http://schemas.microsoft.com/office/2009/07/customui")
$DeleteNodes = $xRoot.Descendants($msoNs + "control").where{$_.Attribute("visible").Value -eq "false"}
$DeleteNodes.ForEach{$_.Remove()}
$xRoot.ToString()
# Save the new xml
$XmlPath = [System.IO.Path]::GetTempFileName()
$xRoot.Save($XmlPath)
XDocument has a Load() method for loading a xml file directly so there is no need to use the Parse() method (I was using for the sake of this example).
The only small drawback comparing to [xml] is that you have to consider namespaces.

Related

web.config changes for KB3159706

i spent a few hours trying to code this by myself, but i don't know much about editing the web.config and all the examples i found don't come close to what i need. CHANGE#1 is unique because it does include the typical key=value.
I want to be able to script (PowerShell) the required modifications of Web.Config, only if the values do not already exist.
CHANGE#1:
Onsert this
(if not already there and "true"): multipleSiteBindingsEnabled="true"
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
CHANGE#2:
Insert this if not already there:
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="SSL"
contract="Microsoft.UpdateServices.Internal.IClientWebService" />
<endpoint address="secured"
binding="basicHttpBinding"
bindingConfiguration="SSL"
contract="Microsoft.UpdateServices.Internal.IClientWebService" />
It goes between here:
<services>
<service
name="Microsoft.UpdateServices.Internal.Client"
behaviorConfiguration="ClientWebServiceBehaviour">
<!-- ... CODE FROM CHANGE#2 GOES HERE ... -->
</service>
</services>
This is the code so far for change#1 (not working):
$sWSUSwebConfig = "C:\Program Files\Update Services\WebServices\ClientWebService\Web.Config"
$xFileContent = [Xml](Get-Content $sWSUSwebConfig)
$root = $xFileContent.get_DocumentElement()
foreach ($item in $root."system.serviceModel"."serviceHostingEnvironment") {
if ($item."multipleSiteBindingsEnabled" -ine "true") {
$activeConnection = $root.serviceHostingEnvironment
$activeConnection.SetAttribute("multipleSiteBindingsEnabled", "true")
#$item.add."multipleSiteBindingsEnabled" = "true"
$iKeyFound = $true
}
}
$xFileContent.Save("c:\temp\web.config")
Reference for modifications: step 3 from kb3159706.

BIML GetDropAndCreateDdl creating incorrect lengths for datatypes

When I use GetDropAndCreateDdl to generate CREATE scripts for tables, I get datatypes for columns that are different than what the datatypes actually are.
This results in a package validation error that "the error output has properties that do not match the properties of its corresponding data source column" and a validation status of "VS_NEEDSNEWMETADATA".
If I right-click the connection source, select Show Advanced Editor, and take a look at the Column Mappings, I can see that [Column1] in the Available External Columns list has a different Length than the datatype that was generated in the GetDropAndCreateDdl. I can delete and re-create the metadata mappings, but that is not a viable solution since there are many dataflow tasks.
How do I get GetDropAndCreateDdl to create the correct datatypes with correct lengths?
I am using ImportDB to get the list of tables, metadata, etc.
Environment.biml
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Connections>
<OdbcConnection Name="OdbcSrc_DV" ConnectionString="Dsn=Source-32bit-test;" />
<OleDbConnection Name="OleDbDst_Staging" ConnectionString="Provider=SQLNCLI11;Server=SQL-DEV;Initial Catalog=Source_Staging;Integrated Security=SSPI;" />
</Connections>
<Databases>
<Database Name="Source" ConnectionName="OdbcSrc_DV" />
<Database Name="Source_Staging" ConnectionName="OleDbDst_Staging" />
</Databases>
<Schemas>
<Schema Name="dbo" DatabaseName="Source" />
<Schema Name="dbo" DatabaseName="Source_Staging" />
</Schemas>
</Biml>
CreateTableMetadata.biml
<## import namespace="System.Data" #>
<## import namespace="Varigence.Biml.CoreLowerer.SchemaManagement" #>
<#
var sourceConnection = RootNode.DbConnections["OdbcSrc_DV"];
var importResult = sourceConnection.ImportDB("", "", ImportOptions.ExcludeForeignKey | ImportOptions.ExcludeColumnDefault | ImportOptions.ExcludeViews);
var tableNamesToImport = new List<string>() { "Test_Table" };
#>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Tables>
<# foreach (var table in importResult.TableNodes.Where(item => tableNamesToImport.Contains(item.Name)).OrderBy(item => item.Name)) { #>
<Table Name="<#=table.Name#>" SchemaName="Source.dbo">
<Columns>
<#=table.Columns.GetBiml()#>
</Columns>
<Annotations>
<Annotation AnnotationType="Tag" Tag="SourceSchemaQualifiedName"><#=table.SchemaQualifiedName#></Annotation>
</Annotations>
</Table>
<# } #>
</Tables>
</Biml>
DeployTargetTables.biml
<## template tier="2" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Packages>
<Package Name="MasterTableDeploy" ConstraintMode="Parallel">
<Tasks>
<# foreach (var table in RootNode.Tables) { #>
<ExecuteSQL Name="SQL CREATE <#=table.Name#>" ConnectionName="OleDbDst_Staging">
<DirectInput><#=table.GetDropAndCreateDdl()#></DirectInput>
</ExecuteSQL>
<# } #>
</Tasks>
</Package>
</Packages>
</Biml>
CreateLoadPackages.biml
<## template tier="2" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Packages>
<Package Name="Copy Data" ConstraintMode="Parallel">
<Tasks>
<# foreach (var table in RootNode.Tables) { #>
<ExecuteSQL Name="SQL TRUNCATE <#=table.Name#>" ConnectionName="OleDbDst_Staging">
<DirectInput>TRUNCATE TABLE <#=table.Name#></DirectInput>
</ExecuteSQL>
<Dataflow Name="DFT LOAD <#=table.Schema.Name#>_<#=table.Name#>">
<PrecedenceConstraints>
<Inputs>
<Input OutputPathName="SQL TRUNCATE <#=table.Name#>.Output" />
</Inputs>
</PrecedenceConstraints>
<Transformations>
<OdbcSource Name="ODBC_SRC <#=table.Name#>" Connection="OdbcSrc_DV">
<DirectInput>SELECT <#=table.GetColumnList()#> FROM <#=table.GetTag("SourceSchemaQualifiedName")#></DirectInput>
</OdbcSource>
<OleDbDestination Name="ODBC_DST <#=table.Name#>" ConnectionName="OleDbDst_Staging">
<TableOutput TableName="<#=table.ScopedName#>" />
</OleDbDestination>
</Transformations>
</Dataflow>
<# } #>
</Tasks>
</Package>
</Packages>
</Biml>
Here is the script that gets created from the ODBC source -
And here are the results from INFORMATION_SCHEMA.COLUMNS for the same table -
Have you tried using the newer method: GetDatabaseSchema? I've found it to be much more reliable across different connection types.
http://www.cathrinewilhelmsen.net/2015/07/12/biml-extension-methods-getdatabaseschema/

Finding a CID reference for attachments in Exchange Contacts

I'm crafting a powershell program using EWS that:
Recover some data from a backup contact folder (telephone, url, notes field,...)
Set back those values in default contact folder in the new contact
Everything does work except, of course, attachents that were added to notes fields, still i'm able to download them.
foreach ($attch in $Item.Attachments)
{
$downloadDirectory = ".\attachments"
$attch.Load()
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $attch.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attch.Content, 0, $attch.Content.Length)
$fiFile.Close()
write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attch.Name.ToString()))
}
Or upload them. (it's static here for test purpose)
$att = $item.Attachments.AddFileAttachment("C:\Scripts\myscript\attachments\PowerShell_transcript.20140506143510.txt")
$att.ContentId = 'test'
$att.IsInline=$false
Ideally, i was looking for matching CID references and uplading back attachments using the same one.
<div><font face="Calibri" size="2"><span style="font-size:11pt;">
<img src="cid:0DF540471453B832E300400FF03B0900CFEC1201#1">
SomeDoc.docx
</span></font></div>
Of course, when the object retrieved from exchange, it doesn't have a contentid (I would have guess it should be CID value).
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Microsoft.Exchange.WebServices.Data.FileAttachment</T>
<T>Microsoft.Exchange.WebServices.Data.Attachment</T>
<T>Microsoft.Exchange.WebServices.Data.ComplexProperty</T>
<T>System.Object</T>
</TN>
<ToString>Microsoft.Exchange.WebServices.Data.FileAttachment</ToString>
<Props>
<Nil N="FileName" />
<BA N="Content"></BA>
<B N="IsContactPhoto">false</B>
<S N="Id">AAMkADdmMDBkOTk4LTNlYzMtNDk1MS05ZTdhLWJmOGE.....</S>
<S N="Name">SomeDoc.docx</S>
<S N="ContentType">application/vnd.openxmlformats-officedocument.wordprocessingml.document</S>
<Nil N="ContentId" /> <=== WHERE IS MY VALUE :'(
<Nil N="ContentLocation" />
<I32 N="Size">1764632</I32>
<DT N="LastModifiedTime">2016-01-26T15:08:03+00:00</DT>
<B N="IsInline">false</B>
</Props>
</Obj>
</Objs>
Does someone know a way to retrieve that value ?
Server/mailbox: Exchange 2013 SP1
Thank you !
it doesn't have a contentid (I would have guess it should be CID value).
An Attachment would only have cid if it was on Inline Attachment in a Mime Message https://www.ietf.org/rfc/rfc2111.txt in a contact it would never be valid (eg even the contact picture doesn't have a cid) as your dealing with Exchange Data Type.
Cheers
Glen

Orbeon control details in own component

I'm making my own component, and I want to have oportunity to set some properties in form builder. In all I want to reach effect similar to autocomplete control, where I can set 3 properties (URI, xpath, and relative xpath). I read, that I can do it using control-details markup, but unfortunately it does not work. This is code (I working on davinci tutorial):
<xbl:binding element="fr|tutorial-davinci" id="fr-tutorial-davinci" xxbl:mode="lhha binding value">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder" xmlns:xf="http://www.w3.org/2002/xforms">
<display-name lang="en">davinci-modified</display-name>
<templates>
<instance label=""/>
<view>
<fr:tutorial-davinci id="" appearance="minimal" labelref="#label" xmlns="" resource="" >
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
</fr:tutorial-davinci>
</view>
</templates>
<control-details>
<xf:input>
<xf:label lang="en">Some param</xf:label>
</xf:input>
</control-details>
</metadata>
<xbl:template>
<xf:model>
<xf:instance id="id1"><value/></xf:instance>
</xf:model>
<xf:input ref="instance('id1')" />
</xbl:template>
</xbl:binding>
You must have a ref attribute on<xf:input> which points to an attribute or element where the information will be stored. In autocomplete.xml, there is in particular theresource` attribute, which:
is pointed to by <xf:input>
is present on the <fr:autocomplete> template

suds: incorrect marshaling of Array of Arrays

I try to talk to a load balancer (Zeus ZXTM) with python:
a = client.factory.create('StringArrayArray')
b = client.factory.create('StringArray')
b.value = ['node01:80',]
a.value = [b,]
client.service.addDrainingNodes(['my pool'], a)
But I get the following error:
suds.WebFault: Server raised fault: 'Not an ARRAY reference at /usr/local/zeus/zxtmadmin/lib/perl/Zeus/ZXTM/SOAPBase.pm line 772.
Extract of the schema definition:
<types>
<xsd:schema targetNamespace='http://soap.zeus.com/zxtm/1.0/'
xmlns='http://www.w3.org/2001/XMLSchema'
xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'
xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'>
<xsd:complexType name="StringArray">
<xsd:complexContent>
<xsd:restriction base='SOAP-ENC:Array'>
<xsd:attribute ref='SOAP-ENC:arrayType' wsdl:arrayType='xsd:string[]'/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="StringArrayArray">
<xsd:complexContent>
<xsd:restriction base='SOAP-ENC:Array'>
<xsd:attribute ref='SOAP-ENC:arrayType' wsdl:arrayType='zeusns:StringArray[]'/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
</types>
<message name="addDrainingNodesRequest">
<part name="names" type="zeusns:StringArray" />
<part name="values" type="zeusns:StringArrayArray" />
</message>
<message name="addDrainingNodesResponse"></message>
<portType name="PoolPort">
<operation name="addDrainingNodes">
<documentation>
Add nodes to the lists of draining nodes, for each of the named pools.
</documentation>
<input message="zeusns:addDrainingNodesRequest"/>
<output message="zeusns:addDrainingNodesResponse"/>
</operation>
</portType>
</definitions>
I also tried like this:
client.service.addDrainingNodes(['my pool'], [['node01:80']])
which worked in SOAPpy but now in suds I get:
suds.WebFault: Server raised fault: 'Value isn't an array'
Comparison between what SOAPpy and what suds sends:
SOAPpy (works):
<ns1:addDrainingNodes xmlns:ns1="http://soap.zeus.com/zxtm/1.0/Pool/" SOAP-ENC:root="1">
<v1 SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array">
<item>my pool</item>
</v1>
<v2 SOAP-ENC:arrayType="xsd:list[1]" xsi:type="SOAP-ENC:Array">
<item SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array">
<item>node01:80</item>
</item>
</v2>
</ns1:addDrainingNodes>
suds (doesn't work):
<ns4:addDrainingNodes>
<names xsi:type="ns0:StringArray" ns3:arrayType="ns2:string[1]">
<item xsi:type="ns2:string">my pool</item>
</names>
<values xsi:type="ns0:StringArrayArray" ns3:arrayType="ns0:StringArray[1]">
<item xsi:type="ns2:string">node01:80</item>
</values>
</ns4:addDrainingNodes>
Context:
I'm new to suds and Soap
there's only the SOAP interface to the ZXTM loadbalancer
using python2.6 and suds 0.3.9
we used to use ZSI's SOAPpy, but had issues using it under python 2.6
Edit: Added suds/SOAPpy payloads
After trying
zillions of different arguments to this function
wsdl2py from ZSI
I found out that suds 4.0 offers plugins, that solves this case by hacking, but nonetheless I think that's a suds bug:
class FixArrayPlugin(Plugin):
def sending(self, context):
command = context.envelope.getChild('Body').getChildren()[0].name
if command == 'addDrainingNodes':
context.envelope.addPrefix('xsd', 'http://www.w3.org/1999/XMLSchema')
values = context.envelope.getChild('Body').getChild('addDrainingNodes').getChild('values')
values.set('SOAP-ENC:arrayType', 'xsd:list[1]')
values.set('xsi:type', 'SOAP-ENC:Array')
item = values[0]
item.set('SOAP-ENC:arrayType', 'xsd:list[1]')
item.set('xsi:type', 'SOAP-ENC:Array')
client = Client(wsdl, location=location, plugins=[FixArrayPlugin()])
a = client.factory.create('StringArrayArray')
b = client.factory.create('StringArray')
b.item = ['node01:80']
a.item = [b,]
client.service.addDrainingNodes(['my pool'], a)
I'm looking forward for this issue to be fixed, IMO this should be a one liner
I'm leaving this open as I'm still interested in better alternatives
What looks strange to me is you have to explicitly construct types like 'StringArrayArray' and 'StringArray' - a smart enough SOAP client in a language like python should be able to figure this out via reflection, examining the arguments you pass to the service call. I'm guessing they're declared in the wsdl as something like "SOAP-ENC:Array" - if so they're not intended to be objects. That would also make sense with the error message "Not an ARRAY reference". Have you tried just;
a = [ ['node01:80',], ]
client.service.addDrainingNodes(['my pool'], a)
Or failing that perhaps...
a = client.factory.create('StringArrayArray')
b = ['node01:80',]
a.value = [ b, ]
client.service.addDrainingNodes(['my pool'], a)
Good luck.