Guest Post by Iouri Simernitski: Inserting an image into a Word file from VSTO

Editor's note:  This is a guest post by Iouri Simernitski, a developer on the VSTO team.

Recently someone asked how to insert an image into a Word file from the VSTO DLL’s resources. There are several ways of doing this:

  • Saving the image to a temporary file and using InlineShapes.AddPicture (you’d need to delete the file afterwards)
  • Saving the image to the clipboard and pasting it into the document (you will lose the previous contents of the clipboard)
  • Inserting a PictureBox control with the picture in it (this will not work in Zoom mode)

There is actually a fourth way – call InlineShapes.AddPicture with a URL instead of a file name and implement a mini-Web server in your VSTO application. This is actually quite easy – the Web server will be listening on a private port and would only serve the one image that it is configured with.

Here’s the code:


        private void ThisDocument_Startup(object sender, System.EventArgs e)


            byte[] contents;

            // This GUID will be used in the URL

            Guid unique = Guid.NewGuid();

            // Use any private port in the range 49152–65535

            int portNumber = 50213;



            // Read the new contents from embedded resources

            using (System.IO.Stream resource = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("WordWebServer.ashura.jpg"))


                using (System.IO.BinaryReader reader = new System.IO.BinaryReader(resource))


                    contents = reader.ReadBytes((int)resource.Length);




            // Use any private port in the range 49152–65535

            using (new WordWebServer.MyWebServer(portNumber, unique, contents))


                // the request will land with the WebServer object in this process

                this.Range(ref missing, ref missing).InlineShapes.AddPicture(@"http://localhost:" + portNumber + "/" + unique.ToString(), ref missing, ref missing, ref missing);







using System;

using System.IO;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading;


namespace WordWebServer


    class MyWebServer : IDisposable


        private TcpListener myListener;

        private static readonly Guid terminationGuid = new Guid("{7b0d9a9f-fbec-4fe5-8ea7-fc3b313510fc}");


        byte[] contentsToServe;


        Guid uniqueID;

        int myPort;


        //The constructor which makee the TcpListener start listening on the

        //given port. It also creates a Thread on the method StartListen().

        public MyWebServer(int port, Guid uniqueID, byte[] contents)


            contentsToServe = contents;

            this.uniqueID = uniqueID;

            this.myPort = port;


            //start listing on the given port

            myListener = new TcpListener(IPAddress.Parse(""), port);



            //start the thread which calls the method 'StartListen'

            Thread thread = new Thread(new ThreadStart(StartListen));




        public void StartListen()



            int startPos = 0;

            String errorMessage;



            while (true)


                //Accept a new connection

                using (TcpClient tcpClient = myListener.AcceptTcpClient())


                    if (tcpClient.Connected)




                         "\nClient Connected!!\n==================\nClient IP {0}\n", tcpClient.Client.RemoteEndPoint));


                        NetworkStream stream = tcpClient.GetStream();


                        //make a byte array and receive data from the client

                        Byte[] receivedBytes = new Byte[1024];


                        // Only reading 1024 characters, this is enough to

                        // see if the GUID is there

                        stream.Read(receivedBytes, 0, receivedBytes.Length);


                        //Convert Byte to String

                        string receivedString = Encoding.ASCII.GetString(receivedBytes);


                        // Look for HTTP request

                        startPos = receivedString.IndexOf("HTTP", 1);


                        // Get the HTTP text and version e.g. it will return "HTTP/1.1"

                        string httpVersion = receivedString.Substring(startPos, 8);


                        //At present we will only deal with GET type

                        // if OPTION received, still OK

                        if (!receivedString.StartsWith("GET"))


                            System.Diagnostics.Debug.WriteLine("Only Get Method is supported..");


                            SendHeader(httpVersion, null, 0, "501 Not Implemented", tcpClient);




                        if (receivedString.Contains(terminationGuid.ToString()))


                            SendHeader(httpVersion, null, 0, "200 OK", tcpClient);




                        if (!receivedString.Contains(uniqueID.ToString()))


                            errorMessage = "<H2>404 Error! File Does Not Exist...</H2>";

                            SendHeader(httpVersion, "", errorMessage.Length, "404 Not Found", tcpClient);

                            SendToBrowser(Encoding.ASCII.GetBytes(errorMessage), tcpClient);




                        SendHeader(httpVersion, null, contentsToServe.Length, "200 OK", tcpClient);

                        SendToBrowser(contentsToServe, tcpClient);









        public void SendHeader(string httpVersion, string mimeHeader, int totalBytes, string statusCode, TcpClient tcpClient)


            StringBuilder responseBuilder = new StringBuilder();


            // if Mime type is not provided set default to text/html

            if (string.IsNullOrEmpty(mimeHeader))


                mimeHeader = "text/xml"// Default Mime Type is text/xml




            responseBuilder.Append(' ');


            responseBuilder.AppendLine("Server: VSTOServer");

            responseBuilder.Append("Content-Type: ");


            responseBuilder.AppendLine("Accept-Ranges: bytes");

            responseBuilder.Append("Content-Length: ");




            Byte[] bSendData = Encoding.ASCII.GetBytes(responseBuilder.ToString());


            SendToBrowser(bSendData, tcpClient);


            System.Diagnostics.Debug.WriteLine("Total Bytes : " + totalBytes.ToString());



        public void SendToBrowser(Byte[] data, TcpClient tcpClient)


            if (tcpClient.Connected)


                NetworkStream stream = tcpClient.GetStream();

                stream.Write(data, 0, data.Length);





                System.Diagnostics.Debug.WriteLine("Connection Dropped....");




        #region IDisposable Members


        public void Dispose()


            // Send a termination request to stop the web server

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:" + myPort + "/" + terminationGuid.ToString());


            WebResponse response = request.GetResponse();







Comments (3)
  1. Holy unnecessary hacks, Batman! I hope this doesn’t actually wind up in someone’s production code. I’d be pretty miffed if an Office add-in started listening on a TCP/IP port, regardless of whether or not it was just on the loopback adapter.

  2. Iouri Simernitski says:

    Hi Josh,

    Thanks for your comment!

    This is a hack, but in some cases creation of temporary files is undesirable. For example, you cannot guarantee that the temporary file gets deleted which may be a security issue (e.g. if the file has confidential information). Also, in some cases (such as  Documents.Open), Word would lock the temp file, making it impossible to ever delete it.

    Do you have specific objections to listening to a TCP port from a VSTO customization?

Comments are closed.

Skip to main content