Wednesday, July 6, 2016

Unique name for Infopath Forms


How to add the infopath form with Unique name so that the new forms will get saved with the unique name. So I am going to explain the steps in detail with screenshots. 
  1. Create RetrieveIDs data connection
  2. Create hidden fields for storing the ID and Filename
  3. Create a Submit data connection for your that uses Filename field for submit filename
  4. Disable toolbar options
  5. Create custom button with 5 rules
Create RetrieveIDs data connection Create a RECEIVE data connection in your form template that connects to the Form Library where the form resides.  Unless you want other metadata from this library, only select the ID field when configuring the DC.  On the last page, make sure to deselect the checkbox for “Automatically retrieve data when form is opened.”  We want to retrieve the data at the exact moment before submitting the form just in case someone else has submitted a form while our form was open (Fig 1).

 
Fig 1 – Data connection to retrieve IDs from the SharePoint List 
 
Create hidden fields for storing the ID and Filename Create a numID field (Whole Number) and strFilename field (Text).  Do not set any conditional formatting, rules, or data validation on these.  Add them to the canvas for now and make them read-only (Fig 2).  These will be visible for testing purposes only.

Fig 2 – Create hidden fields for ID and Filename
 
Create a Submit data connection that uses the strFilename field for dynamically creating the filename Create a SUBMIT data connection in your form template that connects to the Form Library where the form resides.  Put in the URL of your Form Library for the “Document Library” field, and choose the strFilename data element for “File name” field by using the fx button.  Check the box for “Allow overwrite if file exists” (Fig 3).

Fig 3 – Creating the Submit data connection for the Form Library
 
Disable toolbar options Click Tools > Form Options > Browser.  Uncheck Save, and Save As (Fig 4).  Submit should be grayed since it has not been configured.  If it has been configured, be sure it is unchecked.  Update should be unchecked by default, so leave it.  I personally uncheck “Views,” since I use views to dynamically route people to certain information based off their identity or the form’s workflow status, but it’s up to you.  If your form is not browser-enabled, use the Open and Save menu in Form Options to uncheck Save and Save As.
 
Fig 4–Disabling toolbar functions for Browser-Enabled Forms
 
Create custom button with 5 rules
 
Fig 5 – The full view of the 5 Rules
 
    1) Query the RetrieveIDs data connection (Fig 6)
  Fig 6 – Querying the RetrieveIDs data connection

 

2) Set numID to the the next incremental ID only with two conditions – there is at least one form already existing in the library and only if the current form has not already been submitted (Fig 7)
  • Set a condition rule to have two conditions
    • For the first, click “Select a field or group,” select the RetrieveIDsdata source from the top pulldown, drill down to the IDdata element, and choose “Number of occurrences of ID” in the bottom pulldown.  Select the operand “is greater than” and type the number 0 in the last box. 
    • For the second condition, simply choose strFilename in the first pulldown and set the operand to “is blank.”
Fig 7 – Setting the incremental ID with conditions
 
    • Create one action that sets numID to the next number higher than the highest ID in the form library (Fig 8).  For the Action, choose “Set a field’s value.”  For the Field, choose numID.  For Value, click the fx button and put in the formula max(@ID) + 1(do not copy and paste this – you must use the Insert Function and Insert Field buttons to make sure it resolves properly.  You can also copy this Xpath string: xdMath:Max(xdXDocument:GetDOM(“RetrieveIDs”)/dfs:myFields/dfs:dataFields/dfs:AutoNumbering/@ID) + 1  
Fig 8 – Setting the ID to the next incremental value
 
What we are accomplishing here is that we are giving our current form the next ID in line before submitting the form.  We are making sure this rule only runs when the library is not empty, because it would cause an error when there is no ID to use for the max(ID) function.  Also, we are only setting this value when the form is brand new, which means it has no strFilename yet.  We have to do this so that if the form is edited, there will be no change to the numID field.  We only want this rule to run when the library is not empty and when the form is brand new.
3) Set numID to 1 upon the single condition that the form library is empty (Fig 9).
  • Set a condition rule to have one condition.  Click “Select a field or group,” select the RetrieveIDsdata source from the top pulldown, drill down to the IDdata element, and choose “Number of occurrences of ID” in the bottom pulldown.  Select the operand “is equal to” and type the number 0 in the last box.
Fig 9 – Setting the Initial ID with a condition  
 
    • Create one action that sets numID to 1 (Fig 10) 
Fig 10 – Setting the ID to 1 for the first form in the library
 

 
What we are accomplishing here is that we are giving our form the first ID of “1” due to the fact that it is the first form to be submitted to the library.  This is only possible when the form is brand new, so there is no need to add a condition related to the strFilename.  All we need to do is check to see if the form library is empty.  This step circumvents the NaN error when using max(ID) and getting a null value back.  Also, notice that this rule is put in line AFTER the Set Next ID step above.  The reason for this is because both rules would run if they were in the opposite order, and you’d be skipping IDs.
4) Edit the existing form without changing numID or strFilename, then close it (Fig 11).
  • Set a condition rule to have one condition stating that strFilename is not blank.
  • Add two actions in this order:
    • Submit to your SharePoint Library Submit data connection
    • Close
  Fig 11 – Editing the form with the submit data connection
 
 
 
Here, we are simply submitting the form back to the library in a manner that will not change any metadata and will overwrite the existing file of the same name.  This is the whole reason for the strFilename field.  We do not want the strFilename to be recreated each time the form is edited.  This rule is placed before the Submit rule for the same reason as stated above.  We need to check first to see if the form has already been submitted.  If so, then we submit using the current strFilename.  If not, then we skip this rule.
5) Submit the current form only if it is brand new and after dynamically creating the strFilename, then close it.
  • Set a condition rule to have one condition stating that strFilename is blank.
  • Add three actions in this order:
    • Set the Value of strFilename (Fig 12) to the concatenated string combining userName() with the numID field.  The formula is concat(userName(), numID).  The Xpath is concat(xdUser:get-UserName(), my:numID).
    • Submit to your SharePoint Library Submit data connection
    • Close
Fig 12 – Setting the dynamic filename prior to submission
 
 
 
Here, we are submitting the form to the form library for the first time.  This is why we first dynamically create the strFilename, because the Submit data connection uses this field to create the filename in SharePoint.  We only want to do this step upon first submission, so that is why this rule only runs if the strFilename is blank.  Doing this rule last keeps us from double-submitting, because the Edit rule would run right after the Submit rule due to its condition being met (strFilename is not blank).  You can of course choose any concatenation formula you want, or you can use no formula and simply use the numID value – this part is up to you. After publishing the form template, go to your library, which should be empty with no files ever having been submitted (Fig 13), otherwise the ID of the actual files in the library won’t start with 1, and the InfoPath numID won’t match the SharePoint ID.
Fig 13 – In your new Form Library with no history of records, click New
 
 
 
The new, blank form should look like this (Fig 14):
  Fig 14 – Blank version of the form
 
 
 
Click Submit, and you should be returned to the library showing just one file (Fig 15), and it should have the Name of username1 (i.e. ccobb1).
  Fig 15 – First list item is correctly given the ID of 1
 
Click on that file to re-open it, and it should show the populated fields with numID = 1 and strFilename = ccobb1 (Fig 16).
 
  Fig 16 – Form data fields show proper ID and Filename after submission
 
 
 
Click Submit again, and notice that you go back to the form library, but nothing has changed on the form except the Modified date/time stamp.  The ID did not increment, and a new file was not created, because we were just editing the same file.  Now, click New again, and submit.  You should see a new file with a Name that is equivalent to ccobb2.  Do this several times and edit some of those forms several times to ensure they all behave properly. There is one big problem with all of this, though, and I’ll demonstrate it for you.  This entire concept works well UNLESS all the forms get deleted so that there are no forms remaining.  If you do that, then the next form you create will be given the numID of 1 and the strFilename of ccobb1.  However, in SharePoint, the ID will be the next highest ID in succession after the highest ID that had been created and subsequently deleted.  Deleting files in a SharePoint list does not re-start the ID increment, so keep that in mind.  If you delete all but one of your forms, then the numID solution we have here continues to work.  Here is an pic showing what happens if you delete all but one of the forms and then create a new one (Fig 17).
  Fig 17 – AutoNumbering continues even after records have been deleted as long as there is at least one record in the library
 
Three other major concerns are the scalability issue, the View settings issue, and the heavy use issue.
  • Scalability – The data connection is pulling down the ID of every form in the main view of your form library.  What if you have hundreds or thousands of forms?  You will be pulling down tons of info that will slow the form load when all you need is a single piece of info – the highest ID in the list.  A potential workaround for this is to create a custom view that is set to Sort by Created in Descending Order with a finite Item Limit of 1 (Fig 18).  This always show only ONE item, and that will be the item with the highest ID in the entire list, because IDs are assigned upon creation of the file, so the most recently created file will have the highest ID.  The problem then becomes that you need to create a data connection directly to this view instead of the library itself (DCs to SP lists use the default view).  To do this, you must create an XML data connection pointing to the XML view of that SP view by using this write-up: Populating Form Data from SharePoint List Views.  Believe me, this works, and I use it all the time, but I can imagine if it seems daunting to people who are just trying to figure out the entire solution above.  To be clear, this DC would replace your RetrieveIDs DC from step 1.
Fig 18 – View Settings for showing only the list item with the highest ID#  
 
  • View Settings – Similar to the above issue, the Max(ID) function is only going to return the highest ID that InfoPath sees in the default view.  The default view settings for a form library are to limit the items to 100.  Also, any filtering on the default view would potentially cause InfoPath not to see every ID.  You either have to make sure your default view has EVERY list item visible with no Item limit, or you need to use the above alternative view that shows only the highest ID form.
  • Heavy Use – By heavy use, I mean that new forms are being created very frequently – enough that there is the potential for multiple people to submit new forms within the same moments (the window of time depends on the speed of the system), because this method relies on being able to read the items in the library to get the current Max(ID).  If both get submitted close enough together, there is a chance that they assign themselves the same ID or that the submit fails altogether.  It’s best to submit the doc, retrieve the ID after submission, then submit again (all in one button push), but this only works if you don’t need the ID itself in the filename.

Sunday, June 5, 2016

Getting the files modified by a specified user

If you have any requirement to find out the files which has been modified by a specific user in sharepoint online.

Then here is the powershell script which will serve the purpose.

param
(
    [Parameter(Mandatory=$true)]
    [string]$SPSite,

    [Parameter(Mandatory=$true)]
    [string]$LoginName
)

function GetWebFiles($ctx, $web, $userId)

    Write-Host
    write-host "SITE: $($web.Url)"

    # Create a new custom object to hold our result.
    $siteObject = new-object PSObject

    # Add our data to $contactObject as attributes using the add-member commandlet
    $siteObject | add-member -membertype NoteProperty -name "Site URL" -Value $($web.Url)

    # Save the current $contactObject by appending it to $resultsArray ( += means append a new element to ‘me’)
    $resultsarray += $siteObject
    
    $strQuery = "<View Scope='RecursiveAll'><Query><Where><And>
                <Eq><FieldRef Name='FSObjType' /><Value Type='Integer'>0</Value></Eq>
                <Or><Eq><FieldRef ID='Author' LookupId='True' /><Value Type='Lookup'>$($user.Id)</Value></Eq>
                <Eq><FieldRef ID='Editor' LookupId='True' /><Value Type='Lookup'>$($user.Id)</Value></Eq></Or>
                </And></Where></Query></View>"                  
     
    $lists = $web.Lists
        
    $ctx.Load($lists)
    $ctx.ExecuteQuery()

    foreach ($l in $lists)
    {       
        if ($l.BaseType.ToString() -eq "DocumentLibrary")
        {
            $camlQuery = new-object Microsoft.SharePoint.Client.CamlQuery
            $camlQuery.ViewXml = $strQuery

            $items = $l.GetItems($camlQuery)

            $ctx.Load($items)
            $ctx.ExecuteQuery()
       
            if ($items.Count -gt 0)
            {
                foreach ($item in $items)
                {
                    Write-Host "$($SPSite)$($item["FileRef"])" #"$($web.Url)$($item["FileRef"])"

                    # Create a new custom object to hold our result.
                    $siteObject = new-object PSObject

                    # Add our data to $contactObject as attributes using the add-member commandlet
                    $siteObject | add-member -membertype NoteProperty -name "Site URL" -Value "$($web.Url)$($item["FileRef"])"

                    # Save the current $contactObject by appending it to $resultsArray ( += means append a new element to ‘me’)
                    $resultsarray += $siteObject
                }
            }
        }       
    }
   
    $subWebs = $web.Webs

    $ctx.Load($subWebs)
    $ctx.ExecuteQuery()

    foreach ($subWeb in $subWebs)
    {
        GetWebFiles $ctx $subWeb $userId
    }
}

$username = Read-Host -Prompt "Enter Username"
$password = Read-Host -Prompt "Enter Password" -AsSecureString

$scriptPath = Split-Path $MyInvocation.MyCommand.Path

Add-Type -Path "$($scriptPath)\Microsoft.SharePoint.Client.dll"
Add-Type -Path "$($scriptPath)\Microsoft.SharePoint.Client.Runtime.dll"

# Declare an array to collect our result objects
$resultsarray =@()

try
{
    $teamSites = import-csv “$($scriptPath)\O365DTeamSites.csv”

    ForEach ($item in $teamSites)
    {
        $siteUrl = $item.(“Site URL”)

        #Write-Output “Site URL: $siteUrl”

        try
        {
            $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
   
            $creds = New-Object System.Net.NetworkCredential($username, $password)
            $ctx.Credentials = $creds;
                      
            $user = $ctx.Web.SiteUsers.GetByLoginName($LoginName)
                            
            $rootWeb = $ctx.Site.RootWeb  

            $ctx.Load($rootWeb)
            $ctx.Load($user)
            $ctx.ExecuteQuery()

            GetWebFiles $ctx $rootWeb $user.Id       
        }
        catch [Exception]
        {
            if($($_.Exception.Message) -ne "User cannot be found.")
            {
                write-host "Error checking site: $($siteUrl) -> $($_.Exception.Message)"
            }
            continue;
        }
    }

    $resultsarray| Export-csv $($scriptPath)\UserSites.csv -notypeinformation
}
catch [Exception]
{
    write-host "Error -> $($_.Exception.Message)"
    continue;
}


Happy Coding!!!

Retrieve My sites list in SharePoint online

When i was working on branidng for my sites, then i should get the list of my sites so that i can apply the custom branding to all the my sites.
So, in order to get the list of my sites, i have written the below code.

private static void GetMySites()
        {
            string siteUrl = "<root my site url>";
            string userName = "<Enter tenant admin user name>";
            string password = "<Enter tenant admin password>";

            SecureString securePassword = new SecureString();
            foreach (char c in password)
            {
                securePassword.AppendChar(c);
            }

            using (ClientContext clientContext = new ClientContext(siteUrl))
            {
                clientContext.Credentials = new SharePointOnlineCredentials(userName, securePassword);

                Web web = clientContext.Web;
                clientContext.Load(web);
                clientContext.ExecuteQuery();

                List<SiteEntity> mysites = web.MySiteSearch();

                Console.WriteLine("My sites count: " + mysites.Count);

                string filePath = "D:\\GetMySites\MySites.txt";
                if (!System.IO.File.Exists(filePath))
                {
                    System.IO.File.Create(filePath).Close();
                }

                StringBuilder sbSites = new StringBuilder();
                using (System.IO.TextWriter writer = System.IO.File.CreateText(filePath))
                {
                    foreach (SiteEntity site in mysites)
                    {
                        sbSites = sbSites.Append(site.Url + ";");
                    }
                    writer.WriteLine(sbSites);
                }
            }
        }


Hope this code helps you to get the list of my sites in a text file.

Happy coding :)

Update User Profile properties in SharePoint online

I had a requirement from our client that we need to update some of the user AD properties with the SharePoint user profile properties regularly.

So, for that i have build a console application to update the properties and i have scheduled it as a Windows scheduler.

I have kept all the settings (LDAP connection string, AD admin username, password etc.. ) in the app.config file so that i don't have to specify them in the code.

Below is the App.Config file code.

<appSettings>
    <add key="tenantAdministrationUrl" value=""/>
    <add key="tenantAdminLoginName" value=""/>
    <add key="tenantAdminPassword" value=""/>
    <add key="UserProfileProperties" value="WorkPhone|CellPhone"/>   
    <add key="UserProfilePrimeProperty" value="WorkEmail"/>
    <add key="LDAP" value="LDAP://OU="/>
    <add key="ADAdminUserName" value="" />
    <add key="ADAdminPassword" value="" />
    <add key="Properties" value="telephoneNumber|mobile"/>
    <add key="PrimeProperty" value="mail"/>
  </appSettings>
 


Below is the code to retrieve the settings from the app.config file.
Consts._UserProfilePrimeProperty = ConfigurationManager.AppSettings["UserProfilePrimeProperty"];
Consts.ADAdminUserName = ConfigurationManager.AppSettings["ADAdminUserName"];
Consts.ADAdminPassWord = ConfigurationManager.AppSettings["ADAdminPassWord"];
Consts._ProfileProperties = ConfigurationManager.AppSettings["Properties"];
Consts._PrimeProperty = ConfigurationManager.AppSettings["PrimeProperty"];
Consts._tenantAdministrationUrl = ConfigurationManager.AppSettings["tenantAdministrationUrl"];
Consts._tenantAdminLoginName = ConfigurationManager.AppSettings["tenantAdminLoginName"];
Consts._tenantAdminPassword = ConfigurationManager.AppSettings["tenantAdminPassword"];
Consts._UserProfileProfileProperties = ConfigurationManager.AppSettings["UserProfileProperties"];

Below method will retrieve the AD information.

 
public static void getADInformation()
        {
            string ldap = ConfigurationManager.AppSettings["LDAP"];
            Console.WriteLine(ldap);
            if (!string.IsNullOrEmpty(ldap))
            {
                try
                {
                    using (DirectoryEntry ou = new DirectoryEntry(ldap))
                    {
                        DirectorySearcher searcher = new DirectorySearcher(ou);
                        searcher.Filter = "(&(objectClass=user)(objectCategory=person))";
                        searcher.SearchScope = SearchScope.Subtree;
                        searcher.PageSize = 1000;//This has to be set to return all the results from AD, if not set then it will limit the results to 1000 only

                        string[] tempProp = Consts._ProfileProperties.Split('|');
                        string[] properties = new string[tempProp.Count() + 1];
                        properties[0] = Consts._PrimeProperty;
                        for (int i = 1; i <= tempProp.Count(); i++)
                        {
                            properties[i] = tempProp[i - 1];
                        }

                        foreach (var property in properties)
                        {
                            searcher.PropertiesToLoad.Add(property);
                        }

                        SearchResult result;
                        List<string[]> columnValues = new List<string[]>();

                        searcher.PropertiesToLoad.Add("userPrincipalName");
                        searcher.PropertiesToLoad.Add("samAccountName");

                        SearchResultCollection resultCol = searcher.FindAll();
                        Console.WriteLine("Find all");
                        if (resultCol != null)
                        {
                            Console.WriteLine(resultCol.Count + " Results found in AD");
                            for (int i = 0; i < resultCol.Count; i++)
                            {
                                result = resultCol[i];
                                Console.WriteLine(result.Properties["samAccountName"][0] + "-" + result.Properties["userPrincipalName"][0]);

                                string[] tempPropVal = new string[properties.Count()];
                                int propCnt = 0;
                                //get each property value to array
                                foreach (var property in properties)
                                {
                                    if (result.Properties.Contains(property))
                                    {
                                        tempPropVal[propCnt] = Convert.ToString(result.Properties[property][0]);
                                    }
                                    else
                                    {
                                        tempPropVal[propCnt] = "";
                                    }
                                    propCnt++;
                                }
                                columnValues.Add(tempPropVal);
                            }
                        }

                        string[] tempUserProfileProp = Consts._UserProfileProfileProperties.Split('|');
                        string[] userProfileProperties = new string[tempUserProfileProp.Count() + 1];
                        userProfileProperties[0] = Consts._UserProfilePrimeProperty;
                        for (int i = 1; i <= tempUserProfileProp.Count(); i++)
                        {
                            userProfileProperties[i] = tempUserProfileProp[i - 1];
                        }

                        Consts.UserProdDataCol = new List<Dictionary<string, ExpandoObject>>();

                        foreach (string[] columnValue in columnValues)
                        {
                            dynamic data = new ExpandoObject();
                            //get the first object which is the account
                            string account = columnValue[0];
                            Dictionary<string, ExpandoObject> userProfData = new Dictionary<string, ExpandoObject>();
                            if (!string.IsNullOrEmpty(account))
                            {
                                for (int j = 1; j < userProfileProperties.Count(); j++)
                                {
                                    ((IDictionary<String, Object>)data).Add(userProfileProperties[j], columnValue[j]);
                                }
                                userProfData.Add(account, data);
                                Consts.UserProdDataCol.Add(userProfData);
                            }
                            else
                            {
                                Console.WriteLine(account + " is empty");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace);
                }
            }
        }

Below is the method which will import AD data to user profile.

public static void ImportDatatoCloud(TextWriter writer)
        {
            List<Dictionary<string, ExpandoObject>> dataCollection = Consts.UserProdDataCol;
            int count = 1;
            int totalUsers = dataCollection.Count;
            foreach (var dataCol in dataCollection)
            {
                Generic.LogMessage(string.Format("processing user '{0}' of {1}...", count, totalUsers), Consts.LogLevel.Information);
                var userName = dataCol.Keys.FirstOrDefault();
                dynamic propertySet = dataCol.Values.FirstOrDefault();

                string UserAccountName = Consts._sPOProfilePrefix + userName;

                try
                {
                    using (ClientContext clientContext = new ClientContext(Consts._tenantAdministrationUrl))
                    {
                        SecureString passWord = new SecureString();

                        foreach (char c in Consts._tenantAdminPassword.ToCharArray()) passWord.AppendChar(c);

                        clientContext.Credentials = new SharePointOnlineCredentials(Consts._tenantAdminLoginName, passWord);

                        // Get the people manager instance for tenant context
                        PeopleManager peopleManager = new PeopleManager(clientContext);

                        foreach (var property in propertySet)
                        {
                            string propName = property.Key;
                            string propVal = property.Value;
                            Console.WriteLine(userName + " - " + propName + "->" + propVal);

                            if (propVal.Contains('|'))
                            {
                                // List Multiple values
                                List<string> propValues = propVal.Split('|').ToList<string>();

                                // Update the SPS-Skills property for the user using account name from profile.
                                peopleManager.SetMultiValuedProfileProperty(UserAccountName, propName, propValues);
                            }
                            else
                            {
                                // Update the AboutMe property for the user using account name.
                                peopleManager.SetSingleValueProfileProperty(UserAccountName, propName, propVal);
                            }
                        }

                        clientContext.ExecuteQueryRetry();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Issue in updating the profile properties for :" + UserAccountName);
                    writer.WriteLine("Issue in updating the profile properties for :" + UserAccountName);
                    writer.WriteLine(ex.ToString());
                }

                count++;
            }
            Generic.LogMessage("Processing finished for " + totalUsers + " user profiles. Import Complete!", Consts.LogLevel.Warning);
        }

Points to remember: 
Remember to add the sharepoint online client dll's - Microsoft.SharePoint.Client.dll, Microsoft.SharePoint.Client.Runtime.dll, Microsoft.SharePoint.Client.UserProfiles.dll

Hope this code helps you.

Happy Coding :)



Thursday, October 16, 2014

Create and Configure Search Service Application in SharePoint 2013 using PowerShell



The core search architecture of SharePoint 2013 has a more complex and flexible topology that can be changed more efficiently by using Windows PowerShell. Each Search service application has its own search topology. If you create more than one Search service application in a farm, it’s recommended to allocate dedicated servers for the search topology of each Search service application.

In this blog, we will see how to configure topology for one search service application with multiple search components across 2 servers for redundancy and performance.

#==============================================================
#Search Service Application Configuration Settings
#==============================================================

$SearchApplicationPoolName = " SearchApplicationPool"$SearchApplicationPoolAccountName = "Contoso\Administrator"$SearchServiceApplicationName = "Search Service Application"$SearchServiceApplicationProxyName = "Search Service Application Proxy"$DatabaseServer = "2013-SP"$DatabaseName = "SP2013 Search"$IndexLocationServer1 = "D:\SearchIndexServer1"mkdir -Path $IndexLocationServer1 -Force$IndexLocationServer2 = "D:\SearchIndexServer2"mkdir -Path $IndexLocationServer2 -Force

#==============================================================
#Search Application Pool
#==============================================================

Write-Host -ForegroundColor DarkGray "Checking if Search Application Pool exists"$SPServiceApplicationPool = Get-SPServiceApplicationPool -Identity$SearchApplicationPoolName -ErrorAction SilentlyContinueif (!$SPServiceApplicationPool){ Write-Host -ForegroundColor Yellow "Creating Search Application Pool"$SPServiceApplicationPool = New-SPServiceApplicationPool -Name$SearchApplicationPoolName -Account $SearchApplicationPoolAccountName -Verbose}

#==============================================================
#Search Service Application
#==============================================================

Write-Host -ForegroundColor DarkGray "Checking if SSA exists"$SearchServiceApplication = Get-SPEnterpriseSearchServiceApplication-Identity $SearchServiceApplicationName -ErrorAction SilentlyContinueif (!$SearchServiceApplication){ Write-Host -ForegroundColor Yellow "Creating Search Service Application"$SearchServiceApplication = New-SPEnterpriseSearchServiceApplication -Name$SearchServiceApplicationName -ApplicationPool $SPServiceApplicationPool.Name-DatabaseServer $DatabaseServer -DatabaseName $DatabaseName}Write-Host -ForegroundColor DarkGray "Checking if SSA Proxy exists"$SearchServiceApplicationProxy = Get-SPEnterpriseSearchServiceApplicationProxy-Identity $SearchServiceApplicationProxyName -ErrorAction SilentlyContinueif (!$SearchServiceApplicationProxy){ Write-Host -ForegroundColor Yellow "Creating SSA Proxy"New-SPEnterpriseSearchServiceApplicationProxy -Name$SearchServiceApplicationProxyName -SearchApplication$SearchServiceApplicationName}

#==============================================================
#Start Search Service Instance on Server1
#==============================================================

$SearchServiceInstanceServer1 = Get-SPEnterpriseSearchServiceInstance -local Write-Host -ForegroundColor DarkGray "Checking if SSI is Online on Server1" if($SearchServiceInstanceServer1.Status -ne "Online") { Write-Host -ForegroundColor Yellow "Starting Search Service Instance" Start-SPEnterpriseSearchServiceInstance -Identity $SearchServiceInstanceServer1 While ($SearchServiceInstanceServer1.Status -ne "Online") { Start-Sleep -s 5 } Write-Host -ForegroundColor Yellow "SSI on Server1 is started" }

#==============================================================
#Start Search Service Instance on Server2
#==============================================================

$SearchServiceInstanceServer2 = Get-SPEnterpriseSearchServiceInstance -Identity
"2013-SP-AFCache" Write-Host -ForegroundColor DarkGray "Checking if SSI is Online on Server2" if($SearchServiceInstanceServer2.Status -ne "Online") { Write-Host -ForegroundColor Yellow "Starting Search Service Instance" Start-SPEnterpriseSearchServiceInstance -Identity $SearchServiceInstanceServer2 While ($SearchServiceInstanceServer2.Status -ne "Online") { Start-Sleep -s 5 } Write-Host -ForegroundColor Yellow "SSI on Server2 is started" }

#==============================================================
#Cannot make changes to topology in Active State.#Create new topology to add components
#============================================================== $InitialSearchTopology = $SearchServiceApplication |
Get-SPEnterpriseSearchTopology -Active
$NewSearchTopology = $SearchServiceApplication | New-SPEnterpriseSearchTopology

#==============================================================
#Search Service Application Components on Server1#Creating all components except Index (created later)
#==============================================================

New-SPEnterpriseSearchAnalyticsProcessingComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1 New-SPEnterpriseSearchContentProcessingComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1 New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1 New-SPEnterpriseSearchCrawlComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1
New-SPEnterpriseSearchAdminComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1

#==============================================================
#Search Service Application Components on Server2.#Crawl, Query, and CPC
#==============================================================

New-SPEnterpriseSearchContentProcessingComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2 New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2 New-SPEnterpriseSearchCrawlComponent -SearchTopology
$NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2


Server1
Primary
Server2
Primary
IndexPartition 0
IndexComponent 1

True
IndexComponent 2
False
IndexPartition 1
IndexComponent 3
False

IndexComponent 4

True
IndexPartition 2
IndexComponent 5

True
IndexComponent 6
False
IndexPartition 3
IndexComponent 7
False
IndexComponent 8

True



#==============================================================
#Index Components with replicas
#==============================================================

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1 -IndexPartition 0
-RootDirectory $IndexLocationServer1

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer2 -IndexPartition 0
-RootDirectory $IndexLocationServer2

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer2 -IndexPartition 1
-RootDirectory $IndexLocationServer2

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1 -IndexPartition 1
-RootDirectory $IndexLocationServer1

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1 -IndexPartition 2
-RootDirectory $IndexLocationServer1

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer2 -IndexPartition 2
-RootDirectory $IndexLocationServer2

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer2 -IndexPartition 3
-RootDirectory $IndexLocationServer2

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology
-SearchServiceInstance $SearchServiceInstanceServer1 -IndexPartition 3
-RootDirectory $IndexLocationServer1

#==============================================================
#Setting Search Topology using Set-SPEnterpriseSearchTopology
#==============================================================
Set-SPEnterpriseSearchTopology -Identity $NewSearchTopology

#==============================================================
#Clean-Up Operation
#==============================================================
Write-Host -ForegroundColor DarkGray "Deleting old topology"
Remove-SPEnterpriseSearchTopology -Identity $InitialSearchTopology
-Confirm:$false
Write-Host -ForegroundColor Yellow "Old topology deleted"

#==============================================================
#Check Search Topology
#==============================================================
Get-SPEnterpriseSearchStatus -SearchApplication $SearchServiceApplication -Text
Write-Host -ForegroundColor Yellow "Search Service Application and Topology
is configured!!"
$Server02 = (Get-SPServer "2013-SPHost2").Name
$EnterpriseSearchserviceApplication = Get-SPEnterpriseSearchserviceApplication
$ActiveTopology = $EnterpriseSearchserviceApplication.ActiveTopology.Clone()
$IndexComponent =(New-Object Microsoft.Office.Server.Search.Administration.Topology.IndexComponent
$Server02,1);
$IndexComponent.RootDirectory = "D:\IndexComponent02"



# Server1 is the local server where the script is run.

For fault-tolerance we need to have at least two index components (replicas) for an index partition. Here I create 4 index partition with 8 index components. One index partition can serve up to 10 million items. As a good practice, the primary and secondary replicas should be balanced among the index servers. So we will have Server1 hosting 4 index component (out of which 2 will be primary replicas) and Server2 hosting other 4 index components (2 primary replicas).



* Please note that above cmdlets will not create the primary replicas in the server we want as expected as we are running all the cmdlets at same time without saving the topology. Ideally we should create an index partition in one server and then run Set-SPEnterpriseSearchTopology. This will ensure that the primary replica is created in the server we want. The next time you run the same cmdlet in another server for same index partition will create secondary replica. For more details - http://blogs.technet.com/b/speschka/archive/2012/12/02/adding-a-new-search-partition-and-replica-in-sharepoint-2013.aspx

When the above script is run one after the other to create multiple index partitions and replicas in different servers, you can see in the picture below there is no particular order for creation of replicas in the servers. The Primary and secondary replicas are not created in the servers that we wanted. If you are concerned about primary index component server location, then you should set the topology before you run the cmdlet to create secondary replica in another server.






Now that all search components are created in our new topology. Before setting our new topology we need to activate this topology. Remember that we also have an old topology which is in active state.



We will use Set-SPEnterpriseSearchTopology cmdlet which does some important tasks - Activates the NewTopology [$NewSearchTopology.Activate()], deactivates all other active topologies and sets the NewTopology(Active) as the current Enterprise Search Topology

#==============================================================
   #Setting Search Topology using Set-SPEnterpriseSearchTopology
 #==============================================================
 Set-SPEnterpriseSearchTopology -Identity $NewSearchTopology

After running Set-SPEnterpriseSearchTopology cmdlet, it will look like



As Set-SPEnterpriseSearchTopology has already done most of the job for us we will do one last thing - delete the old topology as its no longer required.

#==============================================================
                 #Clean-Up Operation
 #==============================================================
 Write-Host -ForegroundColor DarkGray "Deleting old topology"
 Remove-SPEnterpriseSearchTopology -Identity $InitialSearchTopology
 -Confirm:$false
 Write-Host -ForegroundColor Yellow "Old topology deleted"


When $SearchServiceApplication | Get-SPEnterpriseSearchTopology cmdlet is run, you will find just one topology (new topology that we created)



#==============================================================
                 #Check Search Topology
 #==============================================================
 Get-SPEnterpriseSearchStatus -SearchApplication $SearchServiceApplication -Text
 Write-Host -ForegroundColor Yellow "Search Service Application and Topology
 is configured!!"

In Central administration, Search service application you will find topology like this;



In your environment to know the numbers for each search components, use this scaling guidelines.



* The New-SPEnterpriseSearchIndexComponent requires folder for storing index files. In multiple server search configuration scenario, New-SPEnterpriseSearchIndexComponent checks the existence of RootDirectory in the wrong server. It checks the existence of the folder only in the machine where PowerShell script is executed; even in those cases when new index component is scheduled for other machine. You will get an error message, New-SPEnterpriseSearchIndexComponent : Cannot bind parameter 'RootDirectory'

There are 2 workarounds for this;

1. Create the folder manually in the machine that runs powershell script as its done in the aforementioned script.

2. Use directly the SP Object model instead of cmdlets.

$Server02 = (Get-SPServer "2013-SPHost2").Name
 $EnterpriseSearchserviceApplication  = Get-SPEnterpriseSearchserviceApplication
 $ActiveTopology = $EnterpriseSearchserviceApplication.ActiveTopology.Clone()
 $IndexComponent =(New-Object Microsoft.Office.Server.Search.Administration.Topology.IndexComponent
 $Server02,1);
 $IndexComponent.RootDirectory = "D:\IndexComponent02"
 $ActiveTopology.AddComponent($IndexComponent)

* A note on removing an index component - If you have more than one active index replica for an index partition, you can remove an index replica by performing the procedure Remove a search component in the article Manage search components in SharePoint Server 2013. You cannot remove the last index replica of an index partition using this procedure. If you have to remove all index replicas from the search topology, you must remove and re-create the Search service application and create a completely new search topology that has the reduced number of index partitions.





Tuesday, October 14, 2014

Get List item values in ItemAdding event handler

In an Item Adding event handler Properties.ListItem will be null. Same is the case with Properties.BeforeProperties & Properties.AfterProperties too. So how to get the values entered in the NewForm.aspx in the ItemAdding method. Here we go..
You can access the value using AfterProperties property. But here you have to pass the internal name of the column whose value you want to access.
properties.AfterProperties["Internal Name of the column"]
Or in case if you don’t know/want to explicitly specify the internal name you can get the same using Field.InternalName property. The below code snippet I used explains the same.
string employeeInternalName = Convert.ToString(site.Lists[properties.ListId].Fields["EmployeeName"].InternalName);
string employeeName= Convert.ToString(properties.AfterProperties[employeeInternalName]);