To continue from my last post. Now that I could query the customer AD I wanted to fetch all users belonging to a certain group. I found a lot of "solutions" of how to construct my LDAP query through google though non worked.
In the end, with a lot of trial and error, I managed to get it working. This is the way you query for members in a group using LDAP:
First find our the DN of your group (replace group1 with the name of your group):
SELECT sAMAccountName, distinguishedName
FROM OPENQUERY(ADSI2,'<LDAP://server.customer.local/DC=customer,DC=local>;(&(objectClass=group)(cn=group1));sAMAccountName,distinguishedName;subtree')
Now copy the distinguishedName from the results and replace <distinguishedName> in the following query:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI3,'<LDAP://server.customer.local/DC=customer,DC=local>;(&(objectcategory=person)(objectClass=user)(memberOf=<distinguishedName>));sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
There you are, all users in group "group1"
This blog is a collection of solutions to problems I encounter within my work, currently mostly .NET development and SharePoint. However, fairly often I happen to enter other areas such as Active Directory. I write to collect my solutiuons mostly for my self but if you find it helpfull great!
Friday, May 13, 2011
Query Trusted AD using Linked Server
I'm involved in a project where we query AD for user information from an SQL server and use this information in our product. As a new feature our customer wants to let their customers have some limited access to the system. To facilitate this a new customer AD (New Forrest) have been created where all customers will live. From our side we then needed to fetch the AD information from this AD as well into the SQL server. Since I never worked with LDAP or Linked Servers before it took some time to figure out how to do this.
The before situation:
Our client have 2 AD's, client.local and customer.local in different forests. There is a one way trust. A SQL server is located in client.local and is using a Linked Server to query information from client.local. The Linked Server used to query client.local uses a remote login for all users.
The new things wanted:
The customer.local AD should also be queried from the SQL server in client.local.
We already had a Linked Server up and running since we where already querying client.local. I tried to use that linked server running the following query:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI,'<LDAP://DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
This gave me the following error:
Msg 7399, Level 16, State 1, Line 2
The OLE DB provider "ADsDSOObject" for linked server "ADSI2" reported an error. The provider indicates that the user did not have the permission to perform the operation.
Msg 7321, Level 16, State 2, Line 2
An error occurred while preparing the query "<LDAP://DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree" for execution against OLE DB provider "ADsDSOObject" for linked server "ADSI".
Ahh I thought, I need to specify the server. So I ran the following query:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI2,'<LDAP://server.custmer.local/DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
That didn't help either. Still the same error.
Then I thought, since I'm contacting a server in a different AD I should use a login from that AD when running the query. To do that I needed to setup a new Linked Server. To create one run the following stored procedure:
EXEC sp_addlinkedserver @server = N'ADSI2', @srvproduct=N 'Active Directory Service Interfaces', @provider=N'ADSDSOObject', @datasrc=N'adsdatasource'
To set the login run the following stored procedure, replacing the login information below with your own. The user you specify needs read rights and rights to traverse the AD tree.
EXEC sp_addlinkedsrvlogin @rmtsrvname=N'ADSI2',@useself=N'False',@locallogin=NULL,@rmtuser=N'CUSTOMER\UserAccount',@rmtpassword=N'AccountPassword'
Now I expected everything to work so I ran the updated Query below:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI2,'<LDAP://server.custmer.local/DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
and succeeded.
The before situation:
Our client have 2 AD's, client.local and customer.local in different forests. There is a one way trust. A SQL server is located in client.local and is using a Linked Server to query information from client.local. The Linked Server used to query client.local uses a remote login for all users.
The new things wanted:
The customer.local AD should also be queried from the SQL server in client.local.
We already had a Linked Server up and running since we where already querying client.local. I tried to use that linked server running the following query:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI,'<LDAP://DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
This gave me the following error:
Msg 7399, Level 16, State 1, Line 2
The OLE DB provider "ADsDSOObject" for linked server "ADSI2" reported an error. The provider indicates that the user did not have the permission to perform the operation.
Msg 7321, Level 16, State 2, Line 2
An error occurred while preparing the query "<LDAP://DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree" for execution against OLE DB provider "ADsDSOObject" for linked server "ADSI".
Ahh I thought, I need to specify the server. So I ran the following query:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI2,'<LDAP://server.custmer.local/DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
That didn't help either. Still the same error.
Then I thought, since I'm contacting a server in a different AD I should use a login from that AD when running the query. To do that I needed to setup a new Linked Server. To create one run the following stored procedure:
EXEC sp_addlinkedserver @server = N'ADSI2', @srvproduct=N 'Active Directory Service Interfaces', @provider=N'ADSDSOObject', @datasrc=N'adsdatasource'
To set the login run the following stored procedure, replacing the login information below with your own. The user you specify needs read rights and rights to traverse the AD tree.
EXEC sp_addlinkedsrvlogin @rmtsrvname=N'ADSI2',@useself=N'False',@locallogin=NULL,@rmtuser=N'CUSTOMER\UserAccount',@rmtpassword=N'AccountPassword'
Now I expected everything to work so I ran the updated Query below:
SELECT sAMAccountName, userPrincipalName, Name, department, Manager, title, mobile, ipPhone, mail, distinguishedName, streetAddress, l, postalCode, co, company
FROM OPENQUERY(ADSI2,'<LDAP://server.custmer.local/DC=customer,DC=local>;(objectClass=user);sAMAccountName,Name,Manager,userPrincipalName,title,ipPhone,mail,mobile,department,distinguishedName,streetAddress,l,postalCode,co,company;subtree')
and succeeded.
Webpart error
I'm currently moving our product from a SharePoint 2007 WSS to a SharePoint 2010 Foundation. Most things have gone quite well. Migration of the content DB when without any problems at all. However one thing did happen that caused me some time to solve.A few of the webparts didn't work. I got an error in the webpart with an ID to the SharePoint Logs. Looking into the logs I found the following error message:
Error while executing web part: System.Xml.XmlException: Name cannot begin with the ' ' character, hexadecimal value 0x20. Line 1, position 19.
The webparts that didn't work where all of the same type. A Data web part where we use a web service call to fetch data from a DB. One of the parameters to the web service call is a "where" statement.
The web service ran fine. There was no problem with the where statement. It ran fine both through the web service and through a query window directly to the DB.
After some time working with this problem I found out that SharePoint 2007 and 2010 differs in one small area. In SharePoint 2007 I could write my where statement like this:
State &lt; 70
State is a column in a table. The above would generate a SQL query looking like this:
SELECT * FROM table WHERE State < 70
However in SharePoint 2010 using &lt; or (<) in a WHERE statement like that generates the above mentioned error message.
Knowing this it is obvious that in SharePoint 2010 the XML parser parsing the web service believes that a new tag is starting and finds nothing following the start tag (<) sign and hence responds with the error message.
My solution was to change the where statement to something like this:
State BETWEEN 0 AND 69
After that everything ran fine.
Error while executing web part: System.Xml.XmlException: Name cannot begin with the ' ' character, hexadecimal value 0x20. Line 1, position 19.
The webparts that didn't work where all of the same type. A Data web part where we use a web service call to fetch data from a DB. One of the parameters to the web service call is a "where" statement.
The web service ran fine. There was no problem with the where statement. It ran fine both through the web service and through a query window directly to the DB.
After some time working with this problem I found out that SharePoint 2007 and 2010 differs in one small area. In SharePoint 2007 I could write my where statement like this:
State &lt; 70
State is a column in a table. The above would generate a SQL query looking like this:
SELECT * FROM table WHERE State < 70
However in SharePoint 2010 using &lt; or (<) in a WHERE statement like that generates the above mentioned error message.
Knowing this it is obvious that in SharePoint 2010 the XML parser parsing the web service believes that a new tag is starting and finds nothing following the start tag (<) sign and hence responds with the error message.
My solution was to change the where statement to something like this:
State BETWEEN 0 AND 69
After that everything ran fine.
Thursday, May 5, 2011
SharePoint 2010 - Multiple Domains - People picker
The other day I had to configure a SharePoint 2010 server to support 2 AD using a one way trust. The AD the server it self resides in and a customer AD. I found out that you need to tell SharePoint to look search in other Domains too. Fortunately this is quite simple.
You need to run 2 stsadm commands.
stsadm -o setapppassword -password <yourkey>
Replace <yourkey> with your own key. This key is used to encrypt things(I'll explain what later on). This command should be run on each server in the farm with the exact same Key.
Next you register all the domains you want searched (except the on your server is in). You'll need to do this for each web application, not for each server.
stsadm -o setproperty -pn peoplepicker-searchadforests -pv domain:domainA.local,domainA\account,password -url http://webapplication
Running this command overwrites the previous entry. Fortunately the commands support adding multiple domains. You do that like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv domain:domainB.local,domainB\account,password;domain:domainA.local,domainA\account,password -url http://webapplication
<domain:domainA.local> - this is how you specify what domains to tell SharePoint to search in. You separate different domains with a ";"
<domainA\account,password> - is used to give SharePoint an account to traverse a domain to look for accounts. This is needed to find people by the people picker. The user account needs read rights and rights to traverse the AD tree. Now here the Key set in the first stsadm command comes into play. The key is used as an encryption key to encrypt the password during communication between the SharePoint server and the AD.
It is also possible to add a Forest instead of a domain. You do that like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local,domainA\account,password -url http://webapplication
You could combine forests and domains like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local,domainA\account,password;domain:domainB.local,domainB\account,password -url http://webapplication
If the domains or forests are trusted, it is not necessary to pass in the loginname or password (if you don't mind not finding people from the trusted domain in the people picker). You could then skip that part and your command would look something like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local;domain:domainB.local -url http://webapplication
That's about it I think.
You need to run 2 stsadm commands.
stsadm -o setapppassword -password <yourkey>
Replace <yourkey> with your own key. This key is used to encrypt things(I'll explain what later on). This command should be run on each server in the farm with the exact same Key.
Next you register all the domains you want searched (except the on your server is in). You'll need to do this for each web application, not for each server.
stsadm -o setproperty -pn peoplepicker-searchadforests -pv domain:domainA.local,domainA\account,password -url http://webapplication
Running this command overwrites the previous entry. Fortunately the commands support adding multiple domains. You do that like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv domain:domainB.local,domainB\account,password;domain:domainA.local,domainA\account,password -url http://webapplication
<domain:domainA.local> - this is how you specify what domains to tell SharePoint to search in. You separate different domains with a ";"
<domainA\account,password> - is used to give SharePoint an account to traverse a domain to look for accounts. This is needed to find people by the people picker. The user account needs read rights and rights to traverse the AD tree. Now here the Key set in the first stsadm command comes into play. The key is used as an encryption key to encrypt the password during communication between the SharePoint server and the AD.
It is also possible to add a Forest instead of a domain. You do that like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local,domainA\account,password -url http://webapplication
You could combine forests and domains like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local,domainA\account,password;domain:domainB.local,domainB\account,password -url http://webapplication
If the domains or forests are trusted, it is not necessary to pass in the loginname or password (if you don't mind not finding people from the trusted domain in the people picker). You could then skip that part and your command would look something like this:
stsadm -o setproperty -pn peoplepicker-searchadforests -pv
forest:domainA.local;domain:domainB.local -url http://webapplication
That's about it I think.
Monday, May 2, 2011
Merging Documents
Merging documents can be a bit tricky when you add watermarks into the equation. I had to implement a document merge function that merges a preface page and a main document. This package could then be multiplied several times with different data in the preface page and a watermark on all but the first main document.
UnlinkAndCreateSectionBreak(oDoc)
AddWatermark("Copy", i)
s = GetTempPath() & "Watermarked_"& i & "_" & m_sDocumentName
oDoc.SaveAs(CType(s, Object))
DeleteWatermark(i)
m_oDoc.SaveAs(CType(m_sFullDocumentName,Object))
MergeDocuments(aCCDocuments, s)
End If
Dim oRangeEnd AsMicrosoft.Office.Interop.Word.Range
Dim oHeader As Word.HeaderFooter
Dim oFooter As Word.HeaderFooter
Dim oSection As Microsoft.Office.Interop.Word.Section
oRangeEnd = oDoc.Range
'Insert the SectionBreakNextPage
With oRangeEnd
.Collapse(Word.WdCollapseDirection.wdCollapseEnd)
oParagraph = oDoc.Paragraphs.Add(CType(oRangeEnd, Object))
oParagraph.Range.InsertParagraphAfter()
oParagraph.Range.InsertBreak(Word.WdBreakType.wdSectionBreakNextPage)
End With
'Move to newly created section
oSection = oDoc.Sections.Last
With oSection
'Unlink all headers from previous.
For Each oHeader In .Headers
oHeader.LinkToPrevious = False
Next
'Restart page count if there is one in the header.
.Headers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.RestartNumberingAtSection = True
.Headers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.StartingNumber = 1
'Unlink all footers from previous.
For Each oFooter In .Footers
oFooter.LinkToPrevious = False
Next
'Restart page count if there is one in the footer.
.Footers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.RestartNumberingAtSection = True
.Footers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.StartingNumber = 1
End With
End Sub
Dim sPath As String
End If
End If
oSection = oDoc.Sections.Last
oSection.Range.InsertFile(file)
oSection = m_oDoc.Sections.Last
oSection.Range.InsertFile(sWaterMarked)
i += 1
Next
' Save the merged document.
oDoc.Save()
End Sub
oDoc is the active document.
Dim aCCDocuments AsArrayList
aCCDocuments = NewArrayList()UnlinkAndCreateSectionBreak(oDoc)
AddWatermark("Copy", i)
s = GetTempPath() & "Watermarked_"& i & "_" & m_sDocumentName
oDoc.SaveAs(CType(s, Object))
DeleteWatermark(i)
m_oDoc.SaveAs(CType(m_sFullDocumentName,Object))
If Not GenerateRecipientLetter(m_sDocumentName, aCCDocuments) = False Then
'Merge Documents and printMergeDocuments(aCCDocuments, s)
End If
ClearTempDocuments(s, aCCDocuments)
The first thing I do is to insert a new SectionBreakNextPage and then unlink the headers and footers in the main document. This is needed to allow us to have different headers and footers in the merged document later. The following code explains this process.Private Sub UnlinkAndCreateSectionBreak(ByRef oDoc As Word.Document)
Dim oParagraph As Microsoft.Office.Interop.Word.ParagraphDim oRangeEnd AsMicrosoft.Office.Interop.Word.Range
Dim oHeader As Word.HeaderFooter
Dim oFooter As Word.HeaderFooter
Dim oSection As Microsoft.Office.Interop.Word.Section
oRangeEnd = oDoc.Range
'Insert the SectionBreakNextPage
With oRangeEnd
.Collapse(Word.WdCollapseDirection.wdCollapseEnd)
oParagraph = oDoc.Paragraphs.Add(CType(oRangeEnd, Object))
oParagraph.Range.InsertParagraphAfter()
oParagraph.Range.InsertBreak(Word.WdBreakType.wdSectionBreakNextPage)
End With
'Move to newly created section
oSection = oDoc.Sections.Last
With oSection
'Unlink all headers from previous.
For Each oHeader In .Headers
oHeader.LinkToPrevious = False
Next
'Restart page count if there is one in the header.
.Headers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.RestartNumberingAtSection = True
.Headers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.StartingNumber = 1
'Unlink all footers from previous.
For Each oFooter In .Footers
oFooter.LinkToPrevious = False
Next
'Restart page count if there is one in the footer.
.Footers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.RestartNumberingAtSection = True
.Footers(Word.WdHeaderFooterIndex.wdHeaderFooterPrimary).PageNumbers.StartingNumber = 1
End With
End Sub
Next we use the AddWatermark method I described in the previous blog post to add a watermark to the document and then save the document in the users %TEMP% folder. GetTempFolder fetches %TEMP% and looks like this:
Private Function GetTempPath() AsStringDim sPath As String
sPath = System.Environment.GetEnvironmentVariable("TEMP")
If sPath = "" Then
sPath = System.Environment.GetFolderPath(Environment.SpecialFolder.InternetCache)End If
If NotsPath.EndsWith("/") Or NotsPath.EndsWith("\") Then
sPath = sPath & "\"End If
GetTempPath = sPath
End FunctionOnce the document is saved we Delete the watermark again and resave the original document. The next step involves creating the preface documents. I will not include any code for this since this part highly depends on your specific needs. The result however is that I create each preface document saves it in the user %TEMP% folder and also adds an entry to an ArrayList (aCCDocuments). This leads upp to the actual merge part.
Private Sub MergeDocuments(ByValaCCDocuments As ArrayList,ByVal sWaterMarked AsString) Dim i As Integer
i = 0
For Eachfile In aCCDocuments
' Insert the files to our template oSection = oDoc.Sections.Last
oSection.Range.InsertFile(file)
oSection = m_oDoc.Sections.Last
oSection.Range.InsertFile(sWaterMarked)
i += 1
Next
' Save the merged document.
oDoc.Save()
End Sub
This part is quite simple. We iterate over the preface documents and insert, after the original main document, the preface document and then a watermarked copy of the original document, then the next preface document and then again a watermarked copy of the original and so on until there are no more preface document. The final thing we do is to save the merged document.
Now all that remains is to clean up a bit. We did create a watermarked copy of the original document. We also created a number of preface documents. These need to be deleted and that is what I do in ClearTempDocuments()
Watermark in a word document
I resently had to implement watermarking of a document in word. One important thing to remember is that a watermark is actually placed in your header or footer section. It might be displayed across the document but it is actually in either the header or footer.
Here is what I did.
I added an AddWatermark function that looks like this:
Dim oSection As Word.Section
For Each oSection In m_oDoc.Sections
For Each CurrentHeader As Word.HeaderFooter In oSection.Headers
'Select the current header.
CurrentHeader.Range.Select()
.Select()
.Name = "ExformaticsWaterMarkObject_" & iSeed.ToString()
.TextEffect.NormalizedHeight = Microsoft.Office.Core.MsoTriState.msoFalse
.Line.Visible = Microsoft.Office.Core.MsoTriState.msoFalse
.Fill.Visible = Microsoft.Office.Core.MsoTriState.msoTrue
.Fill.Solid()
.Fill.ForeColor.RGB = Word.WdColor.wdColorGray25
.Fill.Transparency = 0.4 'Make the watermark transparent
.Rotation = 315 'Tilt of the watermark
.LockAspectRatio = Microsoft.Office.Core.MsoTriState.msoTrue
.Height = m_oUtilites.WordApp.InchesToPoints(2.4)
.Width = m_oUtilites.WordApp.InchesToPoints(4.8)
.WrapFormat.AllowOverlap = CType(True, Integer)
.WrapFormat.Side = Word.WdWrapSideType.wdWrapBoth
.WrapFormat.Type = Word.WdWrapType.wdWrapNone
.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin
.RelativeVerticalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin
.Left = Word.WdShapePosition.wdShapeCenter
.Top = Word.WdShapePosition.wdShapeCenter
End With
iSeed = iSeed + 100
Next
Next
Catch ex As Exception
AddWatermark = False
End Try
End Function
To remove the watermark I added the folowing method:
CurrentHeader.Shapes("ExformaticsWaterMarkObject_" & iSeed.ToString()).Delete()
iSeed = iSeed + 100
Next
Next
End Sub
Here is what I did.
I added an AddWatermark function that looks like this:
Private Function AddWatermark(ByVal WatermarkText As String, ByVal iSeed As Integer) As Boolean
AddWatermark = True
Try
Dim wmShape As Word.ShapeDim oSection As Word.Section
For Each oSection In m_oDoc.Sections
For Each CurrentHeader As Word.HeaderFooter In oSection.Headers
CurrentHeader.Range.Select()
'Create the watermark shape
wmShape = CurrentHeader.Shapes.AddTextEffect(Microsoft.Office.Core.MsoPresetTextEffect.msoTextEffect1, WatermarkText, "Times New Roman", 1, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoFalse, 0, 0, CurrentHeader.Range) 'Set all of the attributes of the watermark
With wmShape.Select()
.Name = "ExformaticsWaterMarkObject_" & iSeed.ToString()
.TextEffect.NormalizedHeight = Microsoft.Office.Core.MsoTriState.msoFalse
.Line.Visible = Microsoft.Office.Core.MsoTriState.msoFalse
.Fill.Visible = Microsoft.Office.Core.MsoTriState.msoTrue
.Fill.Solid()
.Fill.ForeColor.RGB = Word.WdColor.wdColorGray25
.Fill.Transparency = 0.4 'Make the watermark transparent
.Rotation = 315 'Tilt of the watermark
.LockAspectRatio = Microsoft.Office.Core.MsoTriState.msoTrue
.Height = m_oUtilites.WordApp.InchesToPoints(2.4)
.Width = m_oUtilites.WordApp.InchesToPoints(4.8)
.WrapFormat.AllowOverlap = CType(True, Integer)
.WrapFormat.Side = Word.WdWrapSideType.wdWrapBoth
.WrapFormat.Type = Word.WdWrapType.wdWrapNone
.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin
.RelativeVerticalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin
.Left = Word.WdShapePosition.wdShapeCenter
.Top = Word.WdShapePosition.wdShapeCenter
End With
iSeed = iSeed + 100
Next
Next
Catch ex As Exception
AddWatermark = False
End Try
End Function
To remove the watermark I added the folowing method:
Private Sub DeleteWatermark(ByVal iSeed As Integer)
Dim oSection As Word.Section
For Each oSection In m_oDoc.Sections
For Each CurrentHeader As Word.HeaderFooter In oSection.HeadersCurrentHeader.Shapes("ExformaticsWaterMarkObject_" & iSeed.ToString()).Delete()
iSeed = iSeed + 100
Next
Next
End Sub
Subscribe to:
Posts (Atom)