Contents tagged with PowerShell

  • Build JS file for .net Constants class

    Tags: PowerShell, javascript, SharePoint, SharePoint 2013

    It’s pretty common practice for me to have a Constants class in all my projects, especially so in SharePoint projects where you are likely to have Field name, Content Type ID’s and probably some GUID’s that you need.

    As I find myself more and more developing javascript solutions, I want access to those constants on the client side.  So taking inspiration from SharePoint (which generates a Strings object in javascript – see /_layouts/15/<lcid>/string.js, this file is auto generated by stsadm.exe.  I decided to cobble together a little script to build my js file.

    Say you have a .net constants class similar to this (I also use nested classes in my constants class).

    // Note: This file is autogenerated in JS for use on the client side.
    //       Do NOT put any sensitive string values in it.
    public static class MyStrings
    {
        public static class NestedOne
        {
            public static class NestedTwo
            {
                public const string Foo = "FooString";
                public const string Bar = "BarString";
    
                public static class NestedThree
                {
                    public const string Address1 = "AddressLine1";
                }
            }
    
            public static class NestedTwoTwo
            {
                public const string Location = "Location";
            }
    
            public const string AnotherOne = "SomeValue123";
        }
    }

     

    And use this PowerShell scripts (it’s a litle muddly I know, but it does work nicely!)
    Note: You could remove the string builder, its just there to help with debugging.

    The script is going to recursively loop through all your nested classes and build the required javascript file.

    clear
    $dllPath = 'C:\Constants.dll' #Path to dll containing Constants class
    $constantsClassName = "MyStrings" #Name of the .net constants class
    $jsClassName = "MyJsStrings"; #Name of the object in Javascript
    $outputJsFilePath = "C:\dump\MyJsStrings.js" #Place to save output js file
    
    # StringBuilder is just for debugging, you can change to simply write to stream
    $sb = New-Object -TypeName "System.Text.StringBuilder"
    $stream = [System.IO.StreamWriter] $outputJsFilePath
    
    function Append([string] $val) {
    	[Void]$sb.AppendLine($val)
    }
    
    Append "/* Copyright message ... "
    Append " * This file has been auto generated, do NOT edit"
    Append " */"
    
    function WriteFields ([System.Reflection.MemberInfo]$member, $parent) {
    	foreach($f in $member.GetFields() | where { $_.FieldType.Name -eq "String"}) {
    		$t = $jsClassName+"."
    		
    		if(-not [System.String]::IsNullOrWhiteSpace($parent)) {
    			$t += $parent+"."
    		} 
    		$t += $member.Name+"."+$f.Name+"='"+$f[0].GetValue($null)+"';"
    		Append $t
    	}
    	Append ""
    }
    
    function Recurse ([System.Reflection.MemberInfo]$member, $parent) {
    	WriteFields $member $parent
    	
    	foreach($mem in $member.GetMembers() | where { $_.IsClass -eq $true }) {
    		if(-not [System.String]::IsNullOrWhiteSpace($parent)) {
    			Append ($jsClassName+"."+$parent+"."+$member.Name+"."+$mem.Name+"=function(){};")
    			Recurse $mem ($parent+"."+$member.Name)
    		} else {		
    			Append ($jsClassName+"."+$member.Name+"."+$mem.Name+"=function(){};")
    			Recurse $mem $member.Name
    		}
    	}
    }
    
    # Load the dll
    # Doing it this way doesn't lock the file, could also be done with a new AppDomain
    
    $fileBytes = [IO.File]::ReadAllBytes($dllPath)
    
    $assembly = [Reflection.Assembly]::Load($fileBytes)
    
    # Get Constants class, could easily be modified if you have more that one class
    $classes = $assembly.GetTypes() | where { $_.Name -eq $constantsClassName }
    
    Append ("var "+$jsClassName+"=new Object();")
    
    foreach($c in $classes) {
    	foreach($m in $c.GetMembers() | where { $_.IsClass -eq $true }) {
    		$t = $jsClassName+"."+$m.Name+"=function(){};"
    		Append $t $m.Name
    		Recurse $m
    	}
    }
    
    Write $sb.ToString()
    $stream.Write($sb.ToString())
    $stream.Close();
    $stream.Dispose();

     

    The output is something like this…

    /* Copyright message ... 
     * This file has been auto generated, do NOT edit
     */
    var MyJsStrings=new Object();
    MyJsStrings.NestedOne=function(){};
    MyJsStrings.NestedOne.AnotherOne='SomeValue123';
    
    MyJsStrings.NestedOne.NestedTwo=function(){};
    MyJsStrings.NestedOne.NestedTwo.Foo='FooString';
    MyJsStrings.NestedOne.NestedTwo.Bar='BarString';
    
    MyJsStrings.NestedOne.NestedTwo.NestedThree=function(){};
    MyJsStrings.NestedOne.NestedTwo.NestedThree.Address1='AddressLine1';
    
    MyJsStrings.NestedOne.NestedTwoTwo=function(){};
    MyJsStrings.NestedOne.NestedTwoTwo.Location='Location';
    

     

    Add a link to that file in VS or add it to your _reference.js file and you’ll get nice intellisense!!

    Hope you find this as useful as I did!

    more...

  • Newly deployed SharePoint site definitions not found in PowerShell

    Tags: SharePoint 2013, PowerShell, SharePoint

    This is a new problem for me, something that I have done many times in SharePoint 2010.  I deploy a new site definition to SharePoint (in this case from Visual Studio 2012, although I can’t see it will matters), pop into PowerShell (in my case I use PowerGUI) and try to create a site from the new definition.

    However, when in PowerShell, running a script similar to this

    $web = Get-SPWeb $SiteURL
    $templates = $web.GetAvailableWebTemplates(1033)
    $templates | Foreach {
    	Write-Host $_.Name
    }

    You find that your new site definition is not listed in with all the other templates.

    Go and look in SharePoint, create a site and bingo, it’s there.  Weird.

    The problem is around caching in PowerShell sessions.

    Restarting IIS and SQL service didn’t work, so I had a quick Google and found this post.

    What a pain…so I quickly (with the help of this article) created a PowerShell cmdlet.

    Note: Make sure when you run the x64 version of InstallUtil (with from the VS x64 command line or C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe) otherwise you wont find it when you run

    Get-PSSnapin –registered

     

    Confused

    I’m not entirely sure why I have never come up against this before having done it multiple times, perhaps its an update to PowerGUI and the way it handles sessions or perhaps it’s a change in SharePoint 2013 caching.  I don’t know!

     

    Code for the PlugIn

    Copied from the link above!.  Don’t forget to add a reference to System.Management.Automation (C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll) and Microsoft.SharePoint

    [Cmdlet(VerbsCommon.Reset, "SPContext")]
    public class RecycleSPContext : PSCmdlet
    {
            
        [DllImport("kernel32.dll")]
        private static extern IntPtr FreeLibrary(IntPtr library);
    
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    
        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);
            
    
        protected override void ProcessRecord()
        {
            Type sprequestmanager = typeof(SPFarm).Assembly.GetType("Microsoft.SharePoint.SPRequestManager", true, true);
            Type spthreadcontext = sprequestmanager.Assembly.GetType("Microsoft.SharePoint.Utilities.SPThreadContext");
            MethodInfo setcontext = spthreadcontext.GetMethod("Set", BindingFlags.Static | BindingFlags.NonPublic);
            Type[] genericArguments = new Type[] { sprequestmanager };
            MethodInfo setcontextgeneric = setcontext.MakeGenericMethod(genericArguments);
            // set the current sprequest manager to null!
            setcontextgeneric.Invoke(null, new object[] { null });
    
            IntPtr p = GetModuleHandle("OWSSVR.DLL");
            FreeLibrary(p);
            string stsadmPath = SPUtility.GetVersionedGenericSetupPath("ISAPI", 15);
            p = LoadLibrary(stsadmPath + @"\OWSSVR.DLL");
                
            WriteObject("SharePoint context restarted at " + DateTime.Now.ToString());
        }
    
    }

     

    And the Installer

    [RunInstaller(true)]
    public class RecycleSPContextInstaller : PSSnapIn
    {
        public override string Description
        {
            get { return "Recycle the SP Context"; }
        }
        public override string Name
        {
            get { return "RecycleSPContext"; }
        }
        public override string Vendor
        {
            get { return "Vendor Name"; }
        }
    }

    more...

  • Call a PowerShell script from SharePoint feature reciever

    Tags: SharePoint, PowerShell

     

    This is not likely to be something you use on a day to day basis, and I can see how some people might disagree with this method – preferring to use other techniques e.g. SharePoint project types etc in VS.

    I just see this as another way to skin the same cat!  A very handy tool to have in your box.

    I have used this for different things, but provisioning lists and adding some content to those lists a common thing.  It’s much easier to use PowerShell to do certain things, deploying a solution every time is just a pain and not overly good use of time waiting for VS to do its thing and recycle app pools left right and centre – so I work with PowerShell on my dev box.

    This then comes in when deploying to stage or production (yes, at this point you could create the artefacts perfectly in your VS solution, but time is money people…spending hours working and essentially still be at the same point I am now, just doesn’t interest me in the slightest – I have no interest in writing code just for the sake of writing code, it much achieve something).

    However, if my project/solution were to be sold for mass reuse on farms that were totally out of my control – I would not use this method.

    You need to add a reference to System.Management.Automation

    I created a simple class called PowerShellProxy that has a couple of helper methods, but this being the important one!

    public static void RunFromFile(string filePath, bool addSharePointSnapIn)
    {
        #region Arg Validation
    
        if (String.IsNullOrEmpty(filePath))
        {
            throw new ArgumentNullException("filePath", "Cannot be null or empty");
        }
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("File not found", filePath);
        }
    
        #endregion
    
        try
        {
            PowerShell ps = PowerShell.Create();
            if (addSharePointSnapIn)
            {
                ps.AddScript("Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction Continue");
            }
    
            string script = ReadFileContent(filePath);
            ps.AddScript(script);
            var result = ps.Invoke();
    
    

     

    You can then just pass in the file path of your ps1 file and you’re away.  Personally I have created a folder within the Layouts folder in my VS solution – that way it’s nice and neat and easy for source control.

     

    More:

    more...

  • PowerShell script to find where in a site collection a specific feature is enabled.

    Tags: PowerShell, SharePoint

    It's not an uncommon task to want to find out in what webs you have a specific feature enabled.  This little script does just that, taking 2 params (site collection url, feature name) and simple output the SharePoint web url.  Nothing fancy, but certainly a useful script to have in your toolbox!

    A good example of this is where you are using content publishing going from an Enterprise internal farm to a Foundation external farm, certain feature won’t be in the Foundation farm and will cause the Content Publisher to fail, it will be so kind as to tell you what feature is causing the problem, but not where it is!  Someone somewhere has enabled a feature they shouldn’t have!!!

    I’m sure some “one line wizards” could optimise this script – but that’s not my bag – I’m interested in getting the job done for our clients and providing a useful script we can use again and again.

    You could of course add in Disable-SPFeature command if you wanted to do that at the same time…

    #Powershell script to identify where in a site collection a specific feature is enabled
    #Version: 1.0
    #Author: Steve Clements | Perspicuity Ltd
    #Params:
    #$siteCollectionUrl - url of the site collection
    #$featureName - name of the feature you want to find
     
     
    param ([string] $siteCollectionUrl, [string] $featureName)
     
    if(!$siteCollectionUrl-or!$featureName) {
        Write-Host-ForegroundColorRed"You must provide a site collection url and feature name to find!"
        break
    }
     
    Write-Host-BackgroundColorDarkGreen"Feature '"$featureName"' exists in these sites"
     
    Get-SPSite$siteCollectionUrl | ForEach-Object {
        Get-SPWeb-Site$_ | ForEach-Object {
          $f=Get-SPFeature-Web$_ | Where {$_.DisplayName -eq$featureName}
      if($f) {
              Write-Host$_.Url
          }
        }
    }
     
    Write-Host-BackgroundColorDarkGreen"Done"
    

    more...

  • Enable Publishing Approval workflow on all SP Web(s) in an Site collection with Powershell

    Tags: PowerShell, SharePoint

    What you need / What you've got?

    1. Pretty obvious, when a user edits/changes a page, you need someone to check its all good before it goes live! Aka Publishing Approval
    2. You can use the "Publishing site with workflow" template or enable it manually or set it up in your custom site definition.
    3. BUT...you've got an entire site collection full of web sites and you need to do it "en-mass" … step up Powershell.
      1. A common scenario could be you were working in Stage or Pre-go live and having Approval workflow just isn't practical or required. Now its live...you need and want it.

    What you need to do to SharePoint...

    1. Enable Moderation on the library (that is Content Approval in UI)
    2. Enable Minor Versions (that’s is 1.1, 1.2 etc)
    3. Create the Workflow History list and enable the Workflow History feature (hiding this list is optional, but I think its best, showing the list offers nothing to the user, you can access the same history looking at the workflow status screen, which is far more usefully presented)
    4. Create the Workflow Tasks list
    5. Associate the "Publishing Approval" workflow to the library
    6. Set the properties on the WorkFlow association
      1. This is your choice, but I have set AllowManual to true, AutoStartCreate and Change to false
    7. Add the association
    8. You would be forgiven in thinking that’s it, job done...but you will find that the even though the workflow is there, it doesn't the "major check-in" property enabled so the experience for the user is different i.e they have to choose the non-default option to fire the workflow.
    9. You need to set the property DefaultContentApprovalWorkflowId to the id of the Workflow association object, what tricked me up...even though in the UI the property is in the workflow with the other workflow properties, but this property is on the library.
    10. Still not done...now, the workflow is firing great, but every status is "Rejected". Why? Because you need to Update the workflow association data.
    11. For this I cheated and setup 1 workflow on the pages library, with the correct actions and Approvers group, then went into SharePoint Manager and grabbed the xml from there.
    12. Also I set the Enabled property to true and call WorkflowAssociations.Update(myAssociation) to set both new properties…
    13. Yes, that’s it...you are actually done now!!!

    The Magic

    This script is 99% influenced by this post http://bit.ly/AcOa2s, I’ve butchered it and added the extra stuff required… FYI: http://get-spscripts.com is a great resource for SharePoint PowerShell scripts...the scripts published are always of high quality, well commented and explained - bookmark it if you don't like thrashing.

    The crazy colours and rather OTT output is my fault!

    I’ve also put the params at the top – not overly user friendly, but this is perfect for my requirements…you can easily change that!

    $url="<site collection URL>"
    $list="Pages"
    $workFlowName="Publishing Approval"
    $wfassname="Page Approval"
     
     
     
    functionAddWorkflowToLibraries ($SiteCollection, $ListName, $WfName, $WfAssociationName)
    {
       #Get site object and create blank guid to store workflow template ID
        $site=Get-SPSite$SiteCollection
       [Guid]$wfTemplateId=New-ObjectGuid
        
        # Get every sub web
        Get-SPWeb-Site$site | ForEach-Object {
      Write-Host-ForegroundColorWhite-BackgroundColorDarkMagenta"WEB: "$_.Title$_.Url
            
            #Do the following if a list exists with the name specified by the user - e.g., Pages
            if ($_.Lists[$ListName]) {
                Write-Host"PROCESS:"$listName"list"
                
                #
                # Get list and set version properties
                #
                $list=$_.Lists[$ListName]
                $list.EnableModeration =$true
                $list.EnableMinorVersions =$true
                $list.Update()
                
               #Go through each workflow installed in the site to find the correct ID            
                foreach ($wfTemplatein$_.WorkflowTemplates) {
                    if ($wfTemplate.Name -eq$WfName) {
                       $wfTemplateId=$wfTemplate.Id
                   }
               }
                
                #
                #SETUP WORKFLOW HISTORY LIST
                #
                $wfTemplate=$_.WorkflowTemplates[$wfTemplateId]
                if($wfTemplate-eq$null) {
                    Write-Host"ERROR: Workflow '"$WfName"' with ID '"$wfTemplateId"' NOT found"-BackgroundColorRed-ForegroundColorWhite
                }
                else {
                    #Check if the site already has a workflow history list - if not, create it
                if(!$_.Lists["Workflow History"])
                    {
                        Write-Host"CREATE: Workflow History list"-ForegroundColorBlue
                        
                    $_.Lists.Add("Workflow History", "A system library used to store workflow history information that is created in this site.  It is created by the Publishing feature.",
                        "WorkflowHistory", "00BFEA71-4EA5-48D4-A4AD-305CF7030140", 140, "100")
                        
                        if (!$_.Features["00BFEA71-4EA5-48D4-A4AD-305CF7030140"]) {
                            Enable-SPFeature-IdentityWorkflowHistoryList-Url$_.Url
                    }
                        $wfHistory=$_.Lists["Workflow History"]
                        $wfHistory.Hidden =$true
                    $wfHistory.Update()
                    }
                    else
                {
                        Write-Host"FOUND: Workflow History list"-ForegroundColorgreen
                    $wfHistory=$_.Lists["Workflow History"]
                    }
                    
                    #
                    #Check if the site already has a workflow tasks list - if not, create it
                    #
                if(!$_.Lists["Workflow Tasks"]) {
                        $_.Lists.Add("Workflow Tasks", "This system library was created by the Publishing feature to store workflow tasks that are created in this site.", "WorkflowTasks", "BF611337-1591-49f4-BF42-CE7BE53852D8", 107, "100")
                        Write-Host"CREATE: Workflow Tasks list"-ForegroundColorBlue
                }
                    else {
                        Write-Host"FOUND: Workflow Tasks list"-ForegroundColorgreen
                    }
                $wfTasks=$_.Lists["Workflow Tasks"]
                    
                    #
                    #new up workflow association (extra associated data can be added if you have it)
                    #
                $wfAssociation= [Microsoft.SharePoint.Workflow.SPWorkflowAssociation]::CreateListAssociation($wfTemplate, $WfAssociationName, $wfTasks, $wfhistory)
                    $wfAssociation.AllowManual =$true
                    #$wfAssociation.AllowAsyncManualStart = $true
                    $wfAssociation.AutoStartChange =$false
                    $wfAssociation.AutoStartCreate =$false
                    
                    #
                    #Check to see if the association has already been added to the list
                    #
                [guid]$wfId=New-ObjectGuid
                [bool]$wfFound=$false
                    
                    foreach ($wfin$list.WorkflowAssociations) {
                        if ($wf.Name -eq$wfAssociation.Name) {
                            $wfId=$wf.Id
                            write-host"FOUND: Workflow"$wf.Name"already exists on"$list.Title"list in site"$_.Title-ForegroundColorgreen
                        $wfFound=$true
                    }
                    }
                    #If association is already there, ignore the add (and optionally delete it)
                if ($wfFound-eq$true) {
                        #Command to remove existing workflow from list before adding new one, if required
                    #$list.WorkflowAssociations.Remove($wfId)
                    write-host"REMOVE: Workflow"$wfAssociation.Name"from"$list.Title"in site"$_.Title-ForegroundColorMagenta
                }
                    #else, add it to the workflow association to the list
                else
                {
                        #Create the association
                    $list.WorkflowAssociations.Add($wfAssociation) | Out-Null
                        
                        #Set the association data (for approvers and approval steps
                        $wfAssociation.AssociationData =Get-AssociationData
                        #enable is not available until its associated.
                        $wfAssociation.Enabled =$true
                        
                        $list.WorkflowAssociations.Update($wfAssociation)
                        
                        $list.DefaultContentApprovalWorkflowId =$wfAssociation.Id
                        $list.Update()
                        write-host"CREATE: Workflow"$wfAssociation.Name"to"$list.Title"in site"$_.Title-BackgroundColorCyan-ForegroundColorRed
                }
                }
            }
            else {
                Write-Host-BackgroundColorDarkRed-ForegroundColorWhite"No"$listName" list found"
            }
        }
        
       #Dispose of Site object
        $site.Dispose()
    }
    functionGet-AssociationData() {
        return"<dfs:myFields xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`" xmlns:dms=`"http://schemas.microsoft.com/office/2009/documentManagement/types`" xmlns:dfs=`"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution`" xmlns:q=`"http://schemas.microsoft.com/office/infopath/2009/WSSList/queryFields`" xmlns:d=`"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields`" xmlns:ma=`"http://schemas.microsoft.com/office/2009/metadata/properties/metaAttributes`" xmlns:pc=`"http://schemas.microsoft.com/office/infopath/2007/PartnerControls`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"><dfs:queryFields /><dfs:dataFields><d:SharePointListItem_RW><d:Approvers><d:Assignment><d:Assignee><pc:Person><pc:DisplayName>Approvers</pc:DisplayName><pc:AccountId>Approvers</pc:AccountId><pc:AccountType>SharePointGroup</pc:AccountType></pc:Person></d:Assignee><d:Stage xsi:nil=`"true`" /><d:AssignmentType>Serial</d:AssignmentType></d:Assignment></d:Approvers><d:ExpandGroups>false</d:ExpandGroups><d:NotificationMessage /><d:DueDateforAllTasks xsi:nil=`"true`" /><d:DurationforSerialTasks xsi:nil=`"true`" /><d:DurationUnits>Day</d:DurationUnits><d:CC /><d:CancelonRejection>true</d:CancelonRejection><d:CancelonChange>true</d:CancelonChange><d:EnableContentApproval>true</d:EnableContentApproval></d:SharePointListItem_RW></dfs:dataFields></dfs:myFields>"
    }
     
    AddWorkflowToLibraries$url$list$workFlowName$wfassname
    

    more...

  • 1