Thursday, March 10, 2016

Adding a new tab to Component Presentation on Page view

I started working on adding new tab and first skeleton I took from is
Robert Curlettes sample.
http://www.curlette.com/?p=753 thats nice post

1. Open Visual Studio  and create a new Project, ASP.NET Empty Web Project.
2. Add a new Web User Control (ascx file) to the project. Name it GUIPageViewExtension.ascx
AddComponentToBundle.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="AddComponentToBundle.ascx.cs" Inherits="GUIPageViewExtension.AddComponentToBundle" %>

   <div class="se-tab-caption">
  Add Component on this Component Presentation to Bundle
 </div>
 <br/>
  <c:Button class="additembutton button2013" runat="server" Title="<%$ Resources: Tridion.Web.UI.Strings, AddSelectedItemsToBundle %>" Label="<%$ Resources: Tridion.Web.UI.Strings, AddSelectedItemsToBundle %>" CommandName="AddSelectedItemsToBundle" />
 
AddComponentToBundle.ascx.cs
protected void Page_Load(object sender, EventArgs e)
{
   
}
Compile and Build the Solution. 
– Copy the DLL “GUIPageViewExtension.dll” file ‘TRIDIONINSTALLATIONPATH \web\WebUI\WebRoot\bin’. This is the location where all the DLLs associated with a GUI Extension are deployed.
Create a config (AddComponentToBundle.config)
This says put Add to Bundle before "Target Groups" when component presentation is opened in pageview
<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge"
               xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration"
xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions"
               xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">

  <resources cache="true">
    <cfg:filters />
    <cfg:groups>
      <cfg:group name="GE.AddComponentToBundle" merge="always">
        <cfg:fileset>
          <cfg:file type="script">/AddComponentToBundle.js</cfg:file>
        </cfg:fileset>
        <cfg:dependencies>
          <cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
          <cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
        </cfg:dependencies>
      </cfg:group>
    </cfg:groups>
  </resources>
  <definitionfiles />
  <extensions>
    <ext:dataextenders/>
    <ext:editorextensions>
      <ext:editorextension target="CME">
        <ext:editurls/>
        <ext:listdefinitions/>
        <ext:taskbars/>
        <ext:commands/>
        <ext:commandextensions/>        
        <ext:contextmenus/>
        <ext:lists />

        <ext:tabpages>
<ext:add>
<ext:extension insertbefore="targetGroups" assignid="AddComponentToBundle" name="Add to Bundle">
 <ext:control>~/AddComponentToBundle.ascx</ext:control>
 <ext:pagetype>GE.AddComponentToBundle</ext:pagetype>
 <ext:dependencies>
<cfg:dependency>GE.AddComponentToBundle</cfg:dependency>
 </ext:dependencies>
<ext:apply>
<ext:view name="PageView">
 <ext:control id="ComponentPresentationPropertiesTabControl" />
</ext:view>
 </ext:apply>
</ext:extension>
</ext:add>

        </ext:tabpages>
        <ext:toolbars/>
        <ext:ribbontoolbars/>
      </ext:editorextension>
    </ext:editorextensions>
  </extensions>
  <commands>
  <cfg:commandset id="Tridion.GUI.Core">
        <!-- Bundle related commands -->
      <cfg:command name="AddItemToBundle" implementation="Tridion.Cme.Commands.AddItemToBundle" />
 </cfg:commandset>
  </commands>
  <contextmenus />
  <localization />
  <settings>
    <defaultpage/>
    <navigatorurl/>
    <editurls/>
    <listdefinitions />
    <itemicons/>
    <theme>
      <path>theme/</path>
    </theme>
    <customconfiguration />
  </settings>

</Configuration>
Create a JS file AddComponentToBundle.js

Type.registerNamespace("GE");

GE.AddComponentToBundle = function GE$GUIPageViewExtension$AddComponentToBundle(element) {
    console.log('Constructor');
    Tridion.OO.enableInterface(this, "GE.AddComponentToBundle");
    this.addInterface("Tridion.Controls.DeckPage", [element]); 
var p = this.properties;
var controls = p.controls;
p.selectedComponentPresentation;
p.selectedComponentId;
};

GE.AddComponentToBundle.prototype.initialize = function AddComponentToBundle$initialize()
{
    console.log('init');
var p = this.properties;
var c = p.controls;
var element = this.getElement();
c.AddToBundleButton = $controls.getControl($(".additembutton", element), "Tridion.Controls.Button");
    this.callBase("Tridion.Controls.DeckPage", "initialize");
c.list = $controls.getControl($("#ComponentPresentationListItems"), "Tridion.Controls.List");
var list = this.properties.controls.list;
$evt.addEventHandler(list, "selectionchange", this.getDelegate(this.updateView));
$evt.addEventHandler(c.AddToBundleButton, "click", this.getDelegate(this._handleAddToBundleButtonClick));

};

GE.AddComponentToBundle.prototype.select = function AddComponentToBundle$select() {
    var p = this.properties;
    var masterTabControl = $controls.getControl($("#MasterTabControl"), 
                                            "Tridion.Controls.TabControl");
    var compPresTab = masterTabControl.getPage("ComponentPresentationsTab");
    p.selectedComponentPresentation 
                       = compPresTab.getSelectedComponentPresentation();   
    p.selectedComponentId =p.selectedComponentPresentation.getComponentId();    console.log('select');
    this.callBase("Tridion.Controls.DeckPage", "select");
    this.updateView();
};

GE.AddComponentToBundle.prototype.updateView = function AddComponentToBundle$updateView()
{
    console.log('update');
    if (this.isSelected()) 
    {
var p = this.properties;
    var masterTabControl = $controls.getControl($("#MasterTabControl"), 
                                            "Tridion.Controls.TabControl");
    var compPresTab = masterTabControl.getPage("ComponentPresentationsTab");
    p.selectedComponentPresentation 
                       = compPresTab.getSelectedComponentPresentation();
    p.selectedComponentId =p.selectedComponentPresentation.getComponentId();
    console.log('selected')           
    }
};

GE.AddComponentToBundle.prototype._handleAddToBundleButtonClick = function AddComponentToBundle$_handleAddToBundleButtonClick(event)
{
    var p = this.properties;
var selection = new Tridion.Cme.Selection();
var itemId = p.selectedComponentId;
selection.addItem(itemId);
selection.setParentItemUri(itemId);
selection.setProperty("IncludeSharedChildPublications", true);
$cme.executeCommand("AddSelectedItemsToBundle", selection);
};



Tridion.Controls.Deck.registerPageType(GE.AddComponentToBundle, "GE.AddComponentToBundle");
Create folder and copy files
– Create a new Folder in the Editors folder called ‘GUIPageViewExtension’. Full path is TRIDIONINSTALLATIONPATH\web\WebUI\Editors\ GUIPageViewExtension
Eg: D:\Tridion\web\WebUI\Editors GUIPageViewExtension

– Copy AddComponentToBundle.ascx, AddComponentToBundle.js, and AddComponentToBundle.config files to the above folder



Add Extension to System.config

<editor name="GUIPageViewExtension" xmlns="http://www.sdltridion.com/2009/GUI/Configuration">
      <installpath xmlns="http://www.sdltridion.com/2009/GUI/Configuration">D:\Tridion\web\WebUI\Editors\GUIPageViewExtension\</installpath>
      <configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration">AddComponentToBundle.config</configuration>
      <vdir xmlns="http://www.sdltridion.com/2009/GUI/Configuration">GUIPageViewExtension</vdir>
</editor>

Setup Virtual Directory in IIS
– Add VDIR to IIS, point to
GUIPageViewExtension Editor folder TRIDIONINSTALLATIONPATH\web\WebUI\Editors\ GUIPageViewExtension
– The name of the VDIR should match the VDIR node in the system.config file.


Restart IIS

Test
The new Tab should appear in the Page -ComponentPresentattions screen
Editor cannot Add items to shared Bundle (That is put of box tridion behavior) However refer next screenshot


Editor can Add items to Bundle by selecting parent Bundle from 020 Publication in this case



Tuesday, January 12, 2016

Core service standalone windows notification service on content expiration


1.1       Requirements

To generate email notifications for the expired content

1.2       Implementation

A windows service is created to implement above requirement. This service uses Tridion Coreservice API to fetch all the components based on schema “AllFields” and reads “ExpiryDate” metadata of these fetched components. The code checks if the date is within 30 days and sends emails to the creator of this component.  The email also contains link to the content.
The email is sent to the user who had created the content. And email id is fetched from FullName of Tridion User seperated in brackets () Tridion User (tridionuser@sdl.com)

Creation of Windows Service

To interact with Tridion Coreservice Client http://cms.electridion.com/webservices/CoreService2011.svc create a new Windows Service.
From the New Project Dialog Box, choose the Windows service template project and name it ContentExpiryNotification.Service like shown below:

References


To Access CoreService API the following jars need to be referred in the ContentExpiryNotification.Service Project.

The project template automatically adds a component class that is called Service1 by default and inherits fromSystem.ServiceProcess.ServiceBase.
Click the designer. Then, in the Properties window, set the ServiceName property for Service1 to ContentExpiryNotification.
Set the Name property to ContentExpiryNotification. Set the AutoLog property to true.
In the Program.cs, edit the Main method to create an instance of ContentExpiryNotification.
namespace ContentExpiryNotification.Service
{
       static class Program
       {
              /// <summary>
              /// The main entry point for the application.
              /// </summary>
              static void Main()
              {
                      ServiceBase[] ServicesToRun;
                      ServicesToRun = new ServiceBase[]
                      {
                             new ContentExpiryNotification()
                      };
                      ServiceBase.Run(ServicesToRun);
              }
       }
}
 In the next section, you will add a custom event log to your Windows service. Event logs are not associated in any way with Windows services. Here EventLog component is used as an example of the type of components you could add to a Windows service.

To add custom event log functionality to service

1.     In the Solution Explorer, right-click ContentExpiryNotification.cs and select View Designer.
2.     From the Components tab of the Toolbox, drag an EventLog component to the designer.
3.     In the Solution Explorer, right-click ContentExpiryNotification.cs and select View Code.
4.     Edit the constructor to define a custom event log.
To access the constructor in Visual C#, expand the Component Designer generated code region.

public partial class ContentExpiryNotification : ServiceBase
       {
             
              public ContentExpiryNotification()
              {
                      InitializeComponent();
                      // Turn off autologging
                      this.AutoLog = false;
                      // create an event source, specifying the name of a log that
                      // does not currently exist to create a new, custom log
                      if (!System.Diagnostics.EventLog.SourceExists("ContentExpiryNotification"))
                      {
                             System.Diagnostics.EventLog.CreateEventSource(
                                    "ContentExpiryNotification""ContentExpiryNotificationLog");
                      }
                      // configure the event log instance to use this source name
                      eventLog1.Source = "ContentExpiryNotification";

1.3       Implementation Code

OnStart() of Service Execute code

To define what happens when the windows service starts, in the code editor, locate the OnStart method that was automatically overridden when the project is created, and write code to  send out email notifications.
              protected override void OnStart(string[] args)
              {
                      if (args.Length > 0)
                      {

                             for (int i = 0; i < args.Length; i++)
                             {
                                    eventLog1.WriteEntry(args[i]);
                                    bool success = SendExpiryNotifications(args[i]);
                                    if (success)
                                           eventLog1.WriteEntry("Expiry Notifications Sent");
                                    else
                                           eventLog1.WriteEntry("Expiry Notifications Not Sent");
                             }
                      }
                      else

                             eventLog1.WriteEntry("No Args specified");
                      }
              }

Tridion Core service API to generate emails

private  SessionAwareCoreServiceClient m_CoreServiceClient = null;
        public  SessionAwareCoreServiceClient CoreServiceClient
        //private static CoreServiceClient m_CoreServiceClient = null;
        //  public static CoreServiceClient CoreServiceClient
        {
            get
            {


                if (m_CoreServiceClient == null)
                {

                   
                    var netTcpBinding = new NetTcpBinding
                    {
                        MaxReceivedMessageSize = 2147483647,
                        ReaderQuotas = new XmlDictionaryReaderQuotas
                        {
                            MaxStringContentLength = 2147483647,
                            MaxArrayLength = 2147483647
                        }
                    };

                    var remoteAddress = new EndpointAddress("net.tcp://localhost:2660/CoreService/2011/netTcp");
                    m_CoreServiceClient = new SessionAwareCoreServiceClient(netTcpBinding, remoteAddress);

                }
                return m_CoreServiceClient;
            }
            set { m_CoreServiceClient = value; }
        }
         ReadOptions options = new ReadOptions();
         MailMessage _mail = new MailMessage();
        public  bool SendExpiryNotifications(string schemaURI)
        {
            try
            {
                UsingItemsFilterData componentsFilter = new UsingItemsFilterData();
                ItemType[] ItemTypes = new ItemType[1];
                ItemTypes[0] = ItemType.Component;
                componentsFilter.ItemTypes = ItemTypes;
                XElement componentListXML = CoreServiceClient.GetListXml(schemaURI, componentsFilter);
                XmlDocument componentList = new XmlDocument();
                componentList.Load(componentListXML.CreateReader());
                XmlNodeList ComponentNodeList = componentList.SelectNodes("//tcm:Item", NSManager);
                foreach (XmlNode componentNode in ComponentNodeList)
                {

                    //do stuff get metdatada
                    String componentURI = "";
                    if (componentNode.Attributes["ID"] != null)
                    {

                        componentURI = componentNode.Attributes["ID"].Value;
                        ComponentData componentData = (ComponentData)CoreServiceClient.Read(componentURI, options);
                        if (componentData.Metadata != "" || componentData.Metadata != null)
                        {

                            XmlDocument componentMetadataDoc = new XmlDocument();
                            componentMetadataDoc.LoadXml(componentData.Metadata);
                            XmlNode metadataNode = componentMetadataDoc.ChildNodes[0];
                            foreach (XmlNode fieldNode in metadataNode.ChildNodes)
                            {
                                if (fieldNode.Name == "ExpiryDate")
                                {
                                    String expirydate = fieldNode.InnerText;
                                    DateTime dt = Convert.ToDateTime(expirydate);
                                    int noOfDays = GetDaysBetweenDates(dt, System.DateTime.Now);
                                    if (noOfDays < 30)
                                    {
                                         eventLog1.WriteEntry("Mail is being generated for " + componentData.Title);
                                         FullVersionInfo componentVersion = componentData.VersionInfo as FullVersionInfo;
                                         LinkToUserData componentCreator = componentVersion.Creator;
                                         string emailTo = TridionUsers[componentCreator.Title];
                                         //send email notification
                                       
                                         _mail.From = new MailAddress(System.Configuration.ConfigurationManager.AppSettings["MAIL_SENDER"]);
                                         _mail.To.Add(emailTo);
                                         _mail.Subject = System.Configuration.ConfigurationManager.AppSettings["MAIL_SUBJECT"];
                                        _mail.SubjectEncoding = Encoding.UTF8;
                                         String url = String.Format(System.Configuration.ConfigurationManager.AppSettings["CMS_URL"], componentData.Id);
                                         string htmlbody = string.Format(System.Configuration.ConfigurationManager.AppSettings["MAIL_BODY"], componentData.Title, noOfDays, url);
                                         _mail.Body = htmlbody;
                                         _mail.BodyEncoding = Encoding.UTF8;
                                         _mail.IsBodyHtml = true;

                                         SmtpClient client;
                                         String smtpHost = System.Configuration.ConfigurationManager.AppSettings["MAIL_SMTPHOST"];
                                         int smtpPort = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["MAIL_SMTPPORT"]);
                                         if (smtpPort < 1)
                                         {
                                             client = new SmtpClient(smtpHost);
                                         }
                                        else
                                         {
                                             client = new SmtpClient(smtpHost, smtpPort);
                                         }

                                         String enableSSL = System.Configuration.ConfigurationManager.AppSettings["MAIL_ENABLESSL"];
                                         if (!String.IsNullOrEmpty(enableSSL))
                                             client.EnableSsl = Boolean.Parse(enableSSL);
                                             client.SendCompleted += SendCompletedCallback;


                                         String smtpUserName = System.Configuration.ConfigurationManager.AppSettings["MAIL_SMTPUSERNAME"];
                                         String smtpPassword = System.Configuration.ConfigurationManager.AppSettings["MAIL_SMTPPASSWORD"];
                                         if (!String.IsNullOrEmpty(smtpUserName) && !String.IsNullOrEmpty(smtpPassword))
                                         {
                                             client.Credentials = new NetworkCredential(smtpUserName, smtpPassword);
                                         }

                                         try
                                         {
                                             client.Send(_mail);
                                             //Logger.Debug("Email queued for sending to " + _mail.To);
                                         }
                                         catch (SmtpException ex)
                                         {
                                             //log exception            
                                             eventLog1.WriteEntry("Expiry Notifications MailServer Exception" + ex.StackTrace);
                                             eventLog1.WriteEntry("Expiry Notifications MailServer Exception Message" + ex.Message);
                                            

                                         }

                                    }
                                }




                            }
                        }
                    }
                }
                CoreServiceClient.Close();
            }
            catch (Exception e)
            {
                //Exception Occured
                eventLog1.WriteEntry("Expiry Notifications Exception" + e.StackTrace);
                eventLog1.WriteEntry("Expiry Notifications  Exception Message" + e.Message);
                eventLog1.WriteEntry("Expiry Notifications  InnerException Message" + e.InnerException.Message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// method that is called when the Send email is completed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="aceArgs"></param>
        private  void SendCompletedCallback(Object sender, System.ComponentModel.AsyncCompletedEventArgs aceArgs)
        {
            String token = (String)aceArgs.UserState;

            if (aceArgs.Cancelled)
            {
                eventLog1.WriteEntry("EmailHandler.SendCompletedCallback: Send cancelled"+ token);
            }

            if (aceArgs.Error != null)
            {
                eventLog1.WriteEntry("EmailHandler.SendCompletedCallback: Error while sending email"+token+"   "+aceArgs.Error.ToString());
            }
            else
            {
                if (_mail == null)
                {
                    eventLog1.WriteEntry("EmailHandler.SendCompletedCallback: Email [{0}] successfully sent (but mail is null)"+ token);
                }
                else
                {
                    eventLog1.WriteEntry("EmailHandler.SendCompletedCallback: Email [{0}] successfully sent to {1}"+ token+ _mail.To.ToString());
                }
            }
        }
        private  int GetDaysBetweenDates(DateTime firstDate, DateTime secondDate)
        {
            return secondDate.Subtract(firstDate).Days;
        }

        private  XmlNamespaceManager m_NSM;
        public  XmlNamespaceManager NSManager
        {
            get
            {
                if (m_NSM == null)
                {
                    m_NSM = new XmlNamespaceManager(new NameTable());

                    m_NSM.AddNamespace("tcm""http://www.tridion.com/ContentManager/5.0");
                    m_NSM.AddNamespace("xlink""http://www.w3.org/1999/xlink");
                    m_NSM.AddNamespace("xhtml""http://www.w3.org/1999/xhtml");
                    m_NSM.AddNamespace("xsd""http://www.w3.org/2001/XMLSchema");
                    m_NSM.AddNamespace("AllFields""http://cms.electridion.com/schemas/AllFields");

                }

                return m_NSM;
            }
        }
        private static readonly Regex UserEmailRegex = new Regex(@"\(.*\)"RegexOptions.Compiled | RegexOptions.Singleline);
        /// <summary>
        /// Gets the name of the user emails by.
        /// </summary>
        /// <returns></returns>
        public  Dictionary<stringstring> GetUserEmailsByName()
        {
            Dictionary<stringstring> result = new Dictionary<stringstring>();
            UsersFilterData usersFilterData = new UsersFilterData { BaseColumns = ListBaseColumns.IdAndTitle };
            IdentifiableObjectData[] datas = CoreServiceClient.GetSystemWideList(usersFilterData);
            foreach (IdentifiableObjectData data in datas)
            {
                if (data is UserData)
                {
                    UserData user = (UserData)data;
                    if (user.Description.Contains("@"))
                    {
                        string userDescription = user.Description;
                        string userName = user.Title;
                        string userEmail = UserEmailRegex.Match(userDescription).ToString();
                        if (userEmail != "")
                        {
                            //  userDescription = userDescription.Replace(userEmail, "").TrimEnd();
                            userEmail = userEmail.Replace("(""").Replace(")""");
                            if (!result.ContainsKey(userName))
                            {
                                result.Add(userName, userEmail);

                            }
                        }
                    }
                }
            }
            return result;
        }

        private  Dictionary<stringstring> _tridionUsers = new Dictionary<stringstring>();

        /// <summary>
        /// Gets a Tridion User dictionary with key = user name and value = user email.
        /// </summary>
        public  Dictionary<stringstring> TridionUsers
        {
            get
            {
                if (_tridionUsers.Count == 0)
                {

                    _tridionUsers = GetUserEmailsByName();

                }
                else
                {
                    //"Tridion Users called, returning list from Cache.");
                }
                return _tridionUsers;
            }
        }

1.4       Creation of Installer

To run any Tridion Service, A valid Tridion user should exist. All tridion services shipped with product installation runs with “Local System” account. 
To create the installers for service in 4.2
·                     Return to design view for ContentExpiryNotification.
·                     Click the background of the designer to select the service itself, rather than any of its contents.
·                     In the Properties window, click the Add Installer link in the gray area beneath the list of properties. By default, a component class containing two installers is added to your project. The component is namedProjectInstaller, and the installers it contains are the installer for your service and the installer for the service's associated process.
·                     Access design view for ProjectInstaller, and click ServiceInstaller1.
·                     In the Properties window, set the ServiceName property to ContentExpiryNotification.
·                     Set the StartType property to Automatic
·                     Access design view for ProjectInstaller, and click ServiceProcessInstaller1.
·                     Set the Account property to LocalSystem.

               

1.5           Configuration

In Solution Explorer, right-click project and Add new item of type “Application configuration File”. Here the constants are defined.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
       <appSettings>
              <add key="CMS_URL" value="https://cms.electridion.com/WebUI/item.aspx?tcm=16#id={0}" />
              <add key="MAIL_SENDER" value="Tridion Email Notification Agent &lt;tridion@myagent.com&gt;" />
              <add key="MAIL_SUBJECT" value="Component Expiry Notification" />
              <add key="MAIL_BODY" value="The component &lt;b&gt; {0} &lt;/b&gt;  is about to expire in {1} days. &lt;a href={2}&gt;Open Component&lt;/a&gt;" />
              <add key="MAIL_SMTPHOST" value="smtp.gmail.com" />
              <add key="MAIL_SMTPPORT" value="542" />
              <add key="MAIL_SMTPUSERNAME" value="" />
              <add key="MAIL_SMTPPASSWORD" value="" />
              <add key="MAIL_ENABLESSL" value="true" />
       </appSettings>
</configuration>

1.6       To build ContentExpiryNotification Service project

·                     In Solution Explorer, right-click your project and select Properties from the shortcut menu. The project's Property Pages dialog box appears.
·                     In the left pane, select the General tab in the Common Properties folder.
·                     From the Startup object list, choose ContentExpiryNotification.ServiceProgram. Click OK.
·                     Press Ctrl+Shift+B to build the project. 
Now that the project is built, it can be deployed. The deployed folder consists of Config file with constants needed by the program and exe files.
   

1.7       To create a setup project for service built in 1.6

·         On the File menu, point to Add Project, and then choose New Project.
·         In the Project Types pane, select the Setup and Deployment Projects folder.
·         In the Templates pane, select Setup Project. Name the project ServiceSetup.
A setup project is added to the solution. Next you will add the output from the Windows service project, ContentExpiryNotification.exe, to the setup.
·         In Solution Explorer, right-click ServiceSetup, point to Add, then choose Project Output. The Add Project Output Group dialog box appears.
·         ContentExpiryNotification is selected in the Project box.
·         From the list box, select Primary Output, and click OK.
·         Build the project
Browse to the directory where the setup project was saved, and run the ServiceSetup.msi 

Execution of Code (Windows Service)

·         Open the Services Control Manager  In Windows 2000 Professional
·         You should see ContentExpiryNotification listed in the Services section of the window after performing all the steps below
·         Select your service in the list, right-click it, and then click Properties. In “Start Parameters” pass the schema id  and without closing window click start.

Once the above service is started. The emails will be sent to the respective authors. The email content generated for POC is as below

1.8       Logging

Open Server Explorer and access the Event Logs node.
Note: Logs are written under Application

1.9       To uninstall the service

On the Start menu, open Control Panel and click Add/Remove Programs, and then locate your service and click Uninstall.
You can also uninstall the program by right-clicking the program icon for the .msi file and selecting Uninstall.

1.10       References

Simple Windows Service Sample