I have built a form that stores certain contact data on it. I want to include a couple of buttons/functions to keep the user in the form as much as possible versus switching between Outlook components (calendar, mail, etc.).
In this case the user can swap email addresses from separate ListBoxes and when they hit the button it will use the emails within one of them. Using VBS because I'm dealing with custom Outlook forms.
Sub GenerateButton_Click()
'Generates Email with all of the CCs
'Variables
Set FormPage = Item.GetInspector.ModifiedFormPages("Commands")
Set DoSend = FormPage.Controls("DoSendListBox")
mailList = ""
'Generate Email List
For x = 0 to (DoSend.ListCount - 1)
mailList = mailList & DoSend.List(x) & ";"
Next
'Compose Email
Set msg = Application.CreateItem(olMailItem)
msg.Subject = "Hello World!"
msg.To = mailList
End Sub
What Happens
- it compiles
- nothing happens on click
Research
- online forums usually in VBA
- relevant articles use outside connection rather than from within outlook
SOLVED
Note: Click on the "script" option and select the object item. From the new window you can navigate through the classes and from this I was able to find MailItem. You can see all of the methods/properties on the right hand pane.
It Turns out the correct syntax was:
Set msg = Application.CreateItem(MailItem)
msg.Display
I have code to parse emails and add "tel:" links to phone numbers, but the modified email body doesn't get shown in the Outlook Reading Pane until the user manually reloads it (view another email, come back to this one).
I've tried a few hacks like Inspector.Display, and ActiveExplorer.ClearSelection ActiveExplorer.AddToSelection, but I can't get consistent results (Display will open new Inspectors for some users, very undesirable).
I was also going to investigate hooking the event sooner. Somehow accessing the email body before Outlook renders it, to avoid the need to refresh. I'm very new to VSTO, so I don't know what event would have access to the MailItem but happen after a user selects it and before it is rendered. I have thought about only processing new mail, but that doesn't help when I roll out this addin, only going forward.
Here is my current ItemLoad sub:
Private Sub Application_ItemLoad(Item As Object)
Dim myObj As Outlook.MailItem
Dim ob As Object
ob = GetCurrentItem()
If TypeOf ob Is Outlook.MailItem Then
myObj = ob
Dim oldbody As String = myObj.Body
If myObj.HTMLBody.Length > 0 Then
myObj.HTMLBody = RegularExpressions.Regex.Replace(myObj.HTMLBody, "(?<!tel:)(?<![2-9\.])(?<!\>\ )[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", " ${p1}-${p2}-${p3}")
Else
myObj.Body = RegularExpressions.Regex.Replace(myObj.Body, "(?<!tel:)(?<![2-9\.])[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", "tel:${p1}.${p2}.${p3}")
End If
myObj.Save()
refreshCurrentMessage()
End If
End Sub
GetCurrentItem() just returns either objApp.ActiveExplorer.Selection.Item(1) or objApp.ActiveInspector.CurrentItem based on TypeName(objApp.ActiveWindow)
Outlook doesn't reflect changes made through the OOM immediately. You need to switch to another folder/email to get the item refreshed because there is no way to update the item on the fly.
You can use the CurrentFolder property of the Explorer class which allows to set a Folder object that represents the current folder displayed in the explorer. Thus, the view will be switched to another folder. Then you can set the CurrentFolder folder to initial folder.
Also I'd suggest releasing all underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object.
Is there a way to add an attachment to an email you're sending out without the attachment being on the filesystem? From reading over the DOM (http://msdn.microsoft.com/en-us/library/bb175153%28office.12%29.aspx) it says that the attachment source can be either a file path or "an Outlook item that constitutes the attachment". I don't use VBA of the office DOMs all that much and I'm not sure what this is. In addition, all the examples I can find are only giving usage examples by using the filesystem path.
I'm calling this from Word for documents that create themselves by filling out some form fields, then print themselves. I'd like them to go out via E-mail as well, but have no need for a permanent copy of the created file. I do realize that I could save them off to a temp directory, attach the saved file, then delete the file once the mail object is sent. It seems a waste to do this though.
Is there an way I can have Word pass the in-memory Document object off to Outlook to attach to the email?
The answer is no, you cannot attach an in-memory document to an outlook mailitem without saving it to disk first.
Oesor,
I made an assumption that email should get sent automatically, so SendMail method is out.
I tried a couple of things to see whether it would work. In both cases code would be embedded in your Word file.
In pre-2007 Word you could use RoutingSlip functionality:
ActiveDocument.HasRoutingSlip = True 'Creates RoutingSlip
With ActiveDocument.RoutingSlip
.Subject = "email Subject"
.AddRecipient = "recipient#domain.com"
.Delivery = wdAllAtOnce
End With
ActiveDocument.Route
Apparently, this code doesn't work in Word 2007. So instead you can use SendForReview functionality:
ActiveDocument.SendForReview "recipient#domain.com", "email subject", False, True
Email gets sent right away (w/o the popup Outlook window), but a couple of caveats exist: the document has to have a corresponding file - it won't work for a new document that has never been saved, and the first time the recipient opens the attached document from e-mail there may be a popup message about starting the review process.
I hope this helps,
Max.
This should work for you, if the relevant attachment source is an inline item, such as an embedded image. I have not tried it with attached files that are not inline but it may work there too. The basic ideas are:
1) Treat the content of the Emails as a Word document because the native Editor for Outlook is Word.
2) Use Word's Copy and Paste to carry everything around with you via the Clipboard because it is a well tested approach. In the example, I have pasted the new section in at the start in a new Paragraph but you could obviously place it anywhere you want.
The strange thing is, though, (see the Debug.Print) that the Attachments Count in the To document does not change, even though the inline images are all where they should be and can be seen and Sent. Have Outlook fun! (The .olm files in the example are simply Outlook.MailItems that have been saved as Template files. They could just as easily be MailItems from an Outlook folder.)
Private Sub TestAttach()
'Places inline Attachment information into a different MailItem
Dim OlTo As Outlook.MailItem
Dim OlFrom As Outlook.MailItem
Dim DocTo As Word.Document
Dim DocFrom As Word.Document
Dim R As Word.Range
Dim R1 As Word.Range
Dim R2 As Word.Range
Dim lStart As Long
Dim lEnd As Long
Set OlFrom = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeOtherAttachments.oft")
Set OlTo = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeAttachments.oft")
Debug.Print "From file starts with " & OlFrom.Attachments.Count & " attachments."
Debug.Print "To file starts with " & OlTo.Attachments.Count & " attachments."
Set DocFrom = OlFrom.GetInspector.WordEditor
Set DocTo = OlTo.GetInspector.WordEditor
OlFrom.Display
OlTo.Display
Set R2 = DocFrom.Content
With R2.Find 'Note: Find settings are 'sticky' and do not need to be repeated on the next find.
.Forward = True
.Wrap = wdFindStop 'Do not loop back to the start of the document
.Format = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Text = "Start flag for Section with Attachments" 'Find the start of the section to move
.Execute
lStart = R2.Start
.Text = "End flag for Section with Attachments" 'Find the end of the section to move
R2.Collapse wdCollapseEnd
.Execute
lEnd = R2.Start
End With
'OlFrom.Display
Set R2 = DocFrom.Range(lStart, lEnd)
'R2.Select
R2.Copy
Set R = DocTo.Range(1, 1)
R.InsertParagraphBefore
'Place the new inline attachments in the To MailItem
Set R = DocTo.Range(1, 1)
R.Paste
OlTo.Display
Debug.Print OlTo.Attachments.Count; "To file ends with " & OlTo.Attachments.Count & " attachments, the same as the original number but all the inline images show."
End Sub
I have a Nant build script which CruiseControl uses to build a solution on-demand.
However, we only recently got CruiseControl so our official build number is different from what is listed in CruiseControl.
I know CruiseControl injects some properties into build scripts so that I can access the CC build number in the script (CCNetLabel) but how do I pass a value back to CC to use as the build number on the UI screen?
Example, CC says build number 2
nAnt script increments a buildnumber.xml value every build, and the official build number is on 123.
I want the CC UI to show last successful build number: 123, not 2, so how do I pass that value back up?
A custom build labeler is required for this. Perforce is our source control provider and we derive our version number from it. The code is as follows:
/// <summary>
/// Gets the latest change list number from perforce, for ccnet to consume as a build label.
/// </summary>
[ReflectorType( "p4labeller" )]
public class PerforceLabeller : ILabeller
{
// perforce executable (optional)
[ReflectorProperty("executable", Required = false)]
public string P4Executable = "p4.exe";
// perforce port (i.e. myserver:1234)
[ReflectorProperty("port", Required = false)]
public string P4Port = String.Empty;
// perforce user
[ReflectorProperty("user", Required = false)]
public string P4User = String.Empty;
// perforce client
[ReflectorProperty("client", Required = false)]
public string P4Client = String.Empty;
// perforce view (i.e. //Dev/Code1/...)
[ReflectorProperty("view", Required = false)]
public string P4View = String.Empty;
// Returns latest change list
public string Generate( IIntegrationResult previousLabel )
{
return GetLatestChangelist();
}
// Stores latest change list into a label
public void Run( IIntegrationResult result )
{
result.Label = GetLatestChangelist();
}
// Gets the latest change list
public string GetLatestChangelist()
{
// Build the arguments to pass to p4 to get the latest changelist
string theArgs = "-p " + P4Port + " -u " + P4User + " -c " + P4Client + " changes -m 1 -s submitted " + P4View;
Log.Info( string.Format( "Getting latest change from Perforce using --> " + theArgs ) );
// Execute p4
ProcessResult theProcessResult = new ProcessExecutor().Execute( new ProcessInfo( P4Executable, theArgs ) );
// Extract the changelist # from the result
Regex theRegex = new Regex( #"\s[0-9]+\s", RegexOptions.IgnoreCase );
Match theMatch = theRegex.Match( theProcessResult.StandardOutput );
return theMatch.Value.Trim();
}
}
The method, GetLatestChangelist, is where you would probably insert your own logic to talk to your version control system. In Perforce there is the idea of the last changelist which is unique. Our build numbers, and ultimately version numbers are based off of that.
Once you build this (into an assembly dll), you'll have to hook it into ccnet. You can just drop the assembly into the server directory (next to ccnet.exe).
Next you modify your ccnet project file to utilize this labeller. We did this with the default labeller block. Something like the following:
<project>
<labeller type="p4labeller">
<client>myclient</client>
<executable>p4.exe</executable>
<port>myserver:1234</port>
<user>myuser</user>
<view>//Code1/...</view>
</labeller>
<!-- Other project configuration to go here -->
</project>
If you're just wanting the build number to show up in ccnet then you're done and don't really need to do anything else. However, you can access the label in your NAnt script if you wish by using the already provided CCNetLabel property.
Hope this helps some. Let me know if you have any questions by posting to the comments.
Did you try to use some environment variables? I believe CCNet can handle these.
I'll dig a bit on this.
Well I see a solution, quite dirty, but anyhow:
1- Add a defaultlabeller section in your CCNET project definition. It will contains the pattern of the build number you want to display.
2- Within NAnt, have a script to update your configuration file, inserting the build number you want to see.
3- Touch (in the Unix sense) the ccnet.exe.config file so as to make it re-load the projects configuration files.
et voilĂ .
We had this problem as well. I ended up writing a special CC labelling plugin.
If your build numbers are sequential, you can just hack the cruise control state file to give it the correct build number to start with. Your looking for a file called [projectName].state.
I changed the Label element to the correct number and the LastSuccessfulIntegrationLabel to be the new number.
However, we only recently got
CruiseControl so our official build
number is different from what is
listed in CruiseControl.
Sort of along the lines of what gbanfill said, you can tell CC what build numbers to start from, but there's no need to hack the .ser file. You can use the JMX interface to set the current build number to get it in sync with your NAnt build number.
You can also set the default label value to to your current build number, delete the .ser file and restart CC.
But maybe the easiest thing is to write the build number into a property file from NAnt and then use the property file label incrementer to read that file. (Be sure to to set setPreBuildIncrementer="true")
I need to create a repeatable process for deploying SQL Server Reporting Services reports. I am not in favor of using Visual Studio and or Business Development Studio to do this. The rs.exe method of scripting deployments also seems rather clunky. Does anyone have a very elegant way that they have been able to deploy reports. The key here is that I want the process to be completely automated.
We use rs.exe, once we developed the script we have not needed to touch it anymore, it just works.
Here is the source (I slightly modified it by hand to remove sensitive data without a chance to test it, hope I did not brake anything), it deploys reports and associated images from subdirectories for various languages. Also datasource is created.
'=====================================================================
' File: PublishReports.rss
'
' Summary: Script that can be used with RS.exe to
' publish the reports.
'
' Rss file spans from beginnig of this comment to end of module
' (except of "End Module").
'=====================================================================
Dim langPaths As String() = {"en", "cs", "pl", "de"}
Dim filePath As String = Environment.CurrentDirectory
Public Sub Main()
rs.Credentials = System.Net.CredentialCache.DefaultCredentials
'Create parent folder
Try
rs.CreateFolder(parentFolder, "/", Nothing)
Console.WriteLine("Parent folder created: {0}", parentFolder)
Catch e As Exception
Console.WriteLine(e.Message)
End Try
PublishLanguagesFromFolder(filePath)
End Sub
Public Sub PublishLanguagesFromFolder(ByVal folder As String)
Dim Lang As Integer
Dim langPath As String
For Lang = langPaths.GetLowerBound(0) To langPaths.GetUpperBound(0)
langPath = langPaths(Lang)
'Create the lang folder
Try
rs.CreateFolder(langPath, "/" + parentFolder, Nothing)
Console.WriteLine("Parent lang folder created: {0}", parentFolder + "/" + langPath)
Catch e As Exception
Console.WriteLine(e.Message)
End Try
'Create the shared data source
CreateDataSource("/" + parentFolder + "/" + langPath)
'Publish reports and images
PublishFolderContents(folder + "\" + langPath, "/" + parentFolder + "/" + langPath)
Next 'Lang
End Sub
Public Sub CreateDataSource(ByVal targetFolder As String)
Dim name As String = "data source"
'Data source definition.
Dim definition As New DataSourceDefinition
definition.CredentialRetrieval = CredentialRetrievalEnum.Store
definition.ConnectString = "data source=" + dbServer + ";initial catalog=" + db
definition.Enabled = True
definition.EnabledSpecified = True
definition.Extension = "SQL"
definition.ImpersonateUser = False
definition.ImpersonateUserSpecified = True
'Use the default prompt string.
definition.Prompt = Nothing
definition.WindowsCredentials = False
'Login information
definition.UserName = "user"
definition.Password = "password"
Try
'name, folder, overwrite, definition, properties
rs.CreateDataSource(name, targetFolder, True, definition, Nothing)
Catch e As Exception
Console.WriteLine(e.Message)
End Try
End Sub
Public Sub PublishFolderContents(ByVal sourceFolder As String, ByVal targetFolder As String)
Dim di As New DirectoryInfo(sourceFolder)
Dim fis As FileInfo() = di.GetFiles()
Dim fi As FileInfo
Dim fileName As String
For Each fi In fis
fileName = fi.Name
Select Case fileName.Substring(fileName.Length - 4).ToUpper
Case ".RDL"
PublishReport(sourceFolder, fileName, targetFolder)
Case ".JPG", ".JPEG"
PublishResource(sourceFolder, fileName, "image/jpeg", targetFolder)
Case ".GIF", ".PNG", ".BMP"
PublishResource(sourceFolder, fileName, "image/" + fileName.Substring(fileName.Length - 3).ToLower, targetFolder)
End Select
Next fi
End Sub
Public Sub PublishReport(ByVal sourceFolder As String, ByVal reportName As String, ByVal targetFolder As String)
Dim definition As [Byte]() = Nothing
Dim warnings As Warning() = Nothing
Try
Dim stream As FileStream = File.OpenRead(sourceFolder + "\" + reportName)
definition = New [Byte](stream.Length) {}
stream.Read(definition, 0, CInt(stream.Length))
stream.Close()
Catch e As IOException
Console.WriteLine(e.Message)
End Try
Try
'name, folder, overwrite, definition, properties
warnings = rs.CreateReport(reportName.Substring(0, reportName.Length - 4), targetFolder, True, definition, Nothing)
If Not (warnings Is Nothing) Then
Dim warning As Warning
For Each warning In warnings
Console.WriteLine(warning.Message)
Next warning
Else
Console.WriteLine("Report: {0} published successfully with no warnings", targetFolder + "/" + reportName)
End If
Catch e As Exception
Console.WriteLine(e.Message)
End Try
End Sub
Public Sub PublishResource(ByVal sourceFolder As String, ByVal resourceName As String, ByVal resourceMIME As String, ByVal targetFolder As String)
Dim definition As [Byte]() = Nothing
Dim warnings As Warning() = Nothing
Try
Dim stream As FileStream = File.OpenRead(sourceFolder + "\" + resourceName)
definition = New [Byte](stream.Length) {}
stream.Read(definition, 0, CInt(stream.Length))
stream.Close()
Catch e As IOException
Console.WriteLine(e.Message)
End Try
Try
'name, folder, overwrite, definition, MIME, properties
rs.CreateResource(resourceName, targetFolder, True, definition, resourceMIME, Nothing)
Console.WriteLine("Resource: {0} with MIME {1} created successfully", targetFolder + "/" + resourceName, resourceMIME)
Catch e As Exception
Console.WriteLine(e.Message)
End Try
End Sub
Here is the batch to call the rs.exe:
SET ReportServer=%1
SET DBServer=%2
SET DBName=%3
SET ReportFolder=%4
rs -i PublishReports.rss -s %ReportServer% -v dbServer="%DBServer%" -v db="%DBName%" -v parentFolder="%ReportFolder%" >PublishReports.log 2>&1
pause
I used the script #David supplied but I had to add some code (I'm typing this up as an answer, as this would be too long for a comment.
The problem is: if there is already a "shared datasource" attached to a report in the report definition, this is never the same datasource as the one that is created in the script.
This also becomes apparent from the warning emitted by the "CreateReport" method:
The data set '' refers to the shared data source '', which is not published on the report server.
So the data source has to be set explicitly afterwards. I've made the following code changes:
I added a global variable:
Dim dataSourceRefs(0) As DataSource
At the end of the CreateDataSource method, that variable gets filled:
Dim dsr As New DataSourceReference
dsr.Reference = "/" + parentFolder + "/" + db
Dim ds As New DataSource
ds.Item = CType(dsr, DataSourceDefinitionOrReference)
ds.Name = db
dataSourceRefs(0) = ds
And in the PublishReport method, that data source gets explicitly set (after CreateReport has been called):
rs.SetItemDataSources(targetFolder + "/" + reportName.Substring(0, reportName.Length - 4), dataSourceRefs)
Note that this last call is only RS 2005 or higher. If you want to load your reports onto a RS 2000 server, you have to use SetReportDataSources in stead:
rs.SetReportDataSources(targetFolder + "/" + reportName.Substring(0, reportName.Length - 4), dataSourceRefs)
Well not really elegant. We created our own tool that uses the reportingservices2005 web service. We found this to be the most reliable way of getting what we want.
It's not really that difficult and lets you expand it to do other things like creating data sources and folders as required.
I strongly recommend RSScripter. As noted in the overview:
Reporting Services Scripter is a .NET
Windows Forms application that enables
scripting and transfer of all
Microsoft SQL Server Reporting
Services catalog items to aid in
transferring them from one server to
another. It can also be used to easily
move items on mass from one Reporting
Services folder to another on the same
server. Depending on the scripting
options chosen, Reporting Services
Scripter can also transfer all catalog
item properties such as Descriptions,
History options, Execution options
(including report specific and shared
schedules), Subscriptions (normal and
data driven) and server side report
parameters.
I know you say that you're not in favor of the Business Development Studio to do this, but I've found the built-in tools to be very reliable and easy to use.
Have you looked into any Continuous Integration solutions such as CruiseControl.NET? If you are able to deploy Reports using rs.exe then you can setup an automated process in CruiseControl to build and deploy your Reports on a timer or whenever a report is modified.
In our environment, we develop in VS with version control then deploy to DEV SSRS. Once the report is validated, we use ReportSync program to deploy reports from ReportServer DEV to ReportServer PROD. The RS.EXE scripts still have their place, but I have found ReportSync to be a much simpler and agile way to promote a report.
ReportSync:
ReportSync is an open source program free to download and use. It works great for downloading reports in bulk, and it can even push a report from one server to another server.
How to get download the program?
Download the source code files from Github: Phires/ReportSynch, Run VS, Open the solution file (.SLN), compile the program, find the executable file (.EXE) from the C:\Temp\reportsync-master\bin\Release folder. Finally, saved the .EXE somewhere for you to use regularly
How do I copy SSRS reports to a new server if I am not the owner of the reports --> ReportSync answer by nunespascal
How to deploy a report?
Run the executable and the interface will launch.
Use the SOURCE and DESTINATION dialogues to choose a single report, multiple reports, or an entire folder of reports. You can any target folder you would like. (HINT: You can even target the same server if you are wanting to duplicate a report on the same server.)
After making your selections press the Sync button
Go to the target server, and validate the change took effect by reviewing the Changed By Date.
This tool has been very convenient, but I have noticed some quirks. For example when I want to update just one report that already exists in the destination, here is what I have to select-- [Source:Report> Target:Folder> Sync]. WARNING: You might think you would select the target server report to update it, but I have tried this and the report does not get updated.
What else can ReportSync do?
There is also an Export feature, which works marvelously for simply dumping all the RDL files to a folder for me to access. This is helpful in the event you need to migrate the server, add the files to to a VS Solution Project, or do anything else will all the files.
In my testing this program does not migrate other content-- subscriptions, shared data sources, shared data sets. It is just applicable to the report files.
I know this post is old, but I came across it when researching RS.EXE scripts, so I thought I would provide an answer this question.