Cross Site Scripting with SharePoint 2013 REST calls

Today I had to figure out how to query a SharePoint 2013 REST service from another domain.

It took a while to find the correct settings. 😉

There was no list on the internet so I want to post it here as reference. – If you have additions to it please post them in the comments.

My test bed:

  1. I created two web applications
  2. At the root of both web apps I created a “Team Site” site collection.
  3. I uploaded a copy of jQuery to the masterpage catalog of http://fromhere.kc-dev.com .
  4. Also to the masterpage catalog of this site I uploaded a script file named “crosssitescripting.js” containing the REST call to http://tohere.kc-dev.com/_api .
  5. On the homepage of the root site collection of http://fromhere.kc-dev.com I added some script tags to load the script files jQuery.js and crosssitescripting.js. And a div tag for the sub web list.

clip_image001

I opened the homepage of http://fromhere.kc-dev.com in the browser and got an error in the F12 dev tools of the Internet Explorer. As expected.

clip_image002

Now I added some web.config modifications using PowerShell to enable cross site scripting. (Some years ago I wrote a note on that topic: https://blog.kenaro.com/2010/09/02/add-web-config-modification-with-powershell-spwebconfigmodification)

After reloading the site I could see the sub web list:

clip_image003

Here is the content of crosssitescripting.js

$(document).ready(function(){
    $.support.cors = true;
    $.ajax({
        url: "http://tohere.kc-dev.com/_api/Web/Webs",
        type: "GET",
        crossDomain: true,
        dataType: "json",
        headers: { "Accept": "application/json; odata=verbose" },
        xhrFields: { withCredentials: true },
        success: function (response) {
            var ul = $("#weblist").append("<ul/>");
            $(response.d.results).each(function(){
                $("<li>"+this.Url+"</li>").appendTo(ul);
            });
        },
        error: function (xhr, status) {
            debugger;
        }
    });
});

Here is the PowerShell script to add the web.config modifications:

Add-PSSnapin Microsoft.SharePoint.PowerShell -EA 0

$localFarm = Get-SPFarm

$webapp = Get-SPWebApplication "http://tohere.kc-dev.com"

# Remove old web.config modifications of MyAuthenticationProvider
$oldMods = @();
$webapp.WebConfigModifications | ? { $_.Owner -eq "CrossSiteScripting" } | % { 
    $oldMods = $oldMods + $_
}

$oldMods | % { 
    $webapp.WebConfigModifications.Remove($_) 
}

# update the Web Application and apply all existing web.config modifications - this executes the "remove" actions from above
$webapp.Update()
[Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplyWebConfigModifications()

#Wait until web.config modifications finished by timer job
while( (Get-SPTimerJob | ? { $_.Name -eq "job-webconfig-modification"}) -ne $null ) {
    Write-Host "." -NoNewline
    Start-Sleep 1
}

# New web.config modifications for MyAuthenticationProvider
$myModification1 = new-object Microsoft.SharePoint.Administration.SPWebConfigModification
$myModification1.Path = "configuration/system.webServer/httpProtocol/customHeaders"
$myModification1.Name = "add[@name='Access-Control-Allow-Origin'][@value='http://fromhere.kc-dev.com']"
$myModification1.Sequence = 0
$myModification1.Owner = "CrossSiteScripting"
#0 = for the enum value "SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode"
$myModification1.Type = 0
$myModification1.Value = "<add name='Access-Control-Allow-Origin' value='http://fromhere.kc-dev.com' />"
$webapp.WebConfigModifications.Add($myModification1)

$myModification1 = new-object Microsoft.SharePoint.Administration.SPWebConfigModification
$myModification1.Path = "configuration/system.webServer/httpProtocol/customHeaders"
$myModification1.Name = "add[@name='Access-Control-Request-Method'][@value='GET,POST,HEAD,OPTIONS']"
$myModification1.Sequence = 0
$myModification1.Owner = "CrossSiteScripting"
$myModification1.Type = 0
$myModification1.Value = "<add name='Access-Control-Request-Method' value='GET,POST,HEAD,OPTIONS' />"
$webapp.WebConfigModifications.Add($myModification1)

$myModification1 = new-object Microsoft.SharePoint.Administration.SPWebConfigModification
$myModification1.Path = "configuration/system.webServer/httpProtocol/customHeaders"
$myModification1.Name = "add[@name='Access-Control-Request-Headers'][@value='Content-Type,Authorization']"
$myModification1.Sequence = 0
$myModification1.Owner = "CrossSiteScripting"
$myModification1.Type = 0
$myModification1.Value = "<add name='Access-Control-Request-Headers' value='Content-Type,Authorization' />"
$webapp.WebConfigModifications.Add($myModification1)

$myModification1 = new-objectMicrosoft.SharePoint.Administration.SPWebConfigModification
$myModification1.Path = "configuration/system.webServer/httpProtocol/customHeaders"
$myModification1.Name = "add[@name='Access-Control-Allow-Credentials'][@value='true']"
$myModification1.Sequence = 0
$myModification1.Owner = "CrossSiteScripting"
$myModification1.Type = 0
$myModification1.Value = "<add name='Access-Control-Allow-Credentials' value='true' />"
$webapp.WebConfigModifications.Add($myModification1)

$webapp.Update()
[Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplyWebConfigModifications()

#Wait until web.config modifications finished by timer job
while( (Get-SPTimerJob | ? { $_.Name -eq "job-webconfig-modification"}) -ne $null ) {
    Write-Host "." -NoNewline
    Start-Sleep 1
}

Update of PS2EXE: Version 0.3.0.0 Now Supports PowerShell 3.0 and 2.0!

Some time ago I’ve written a little tool called “PS2EXE” that creates .EXE files from PowerShell script files. As mentioned in earlier posts this is no conversation of PS to EXE! The PS2EXE script creates an EXE by using the C# compiler and stores the script as Base64 encoded string inside a tiny PowerShell host application.

Today I’ve updated the PS2EXE script to version 0.3.0.0. Now it supports PowerShell 3.0 and PowerShell 2.0.

https://github.com/ikarstein/ps2exe

(Formerly: http://ps2exe.codeplex.com)

Here are the past blog articles:

There are two new parameters for PS2EXE:

-runtime30

-lcid <int>

Using -runtime30 or by starting PS2EXE in a PowerShell 3.0 environment PS2EXE creates a EXE file by using the C# compiler version 4.0.

Using -runtime20 or by starting PS2EXE in a PowerShell 2.0 environment PS2EXE create a EXE file by using the C# compiler 2.0.

-lcid sets the “culture” of the current thread to the specified value. (See http://msdn.microsoft.com/en-us/library/system.threading.thread.currentuiculture.aspx and http://msdn.microsoft.com/en-us/library/system.threading.thread.currentculture.aspx and http://msdn.microsoft.com/en-US/library/w4deeh00(v=vs.80).aspx and http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx.)

The new version should fix the “Assembly not referenced” error discussed here:

http://ps2exe.codeplex.com/discussions/404546

On Windows 8 or Windows Server 2012 there is PowerShell 3.0 installed by default. On Windows 7 or Windows Server 2008 R2 you can install it using Windows Management Framework 3.0.

With PowerShell 3.0 installed you will always start the 3.0 enviroment by using:

the Start Menu (or Screen)

clip_image001

the “Run” dialog:

clip_image002

the command line:

clip_image003

Execution PS2EXE reports PowerShell version 3.0:

clip_image004

You can start a PowerShell 2.0 enviroment by using this parameter: -version 2.0 for POWERSHELL.EXE

clip_image005

Execution PS2EXE reports PowerShell version 2.0:

clip_image006

Now lets use to create an EXE file using the PS2EXE script…

1. Sample: PowerShell 3.0 without parameters –runtime20 and –runtime30

clip_image007

2. Sample: PowerShell 3.0 with parameter –runtime20

clip_image008

(Behind the scene this starts PowerShell.exe using parameter –version 2.0.)

3. Sample: PowerShell 2.0 without parameters –runtime20 and –runtime30

clip_image009

4. Sample: PowerShell 2.0 with parameter –runtime20

clip_image010

This is not supported!

SharePoint 2013 Design Packages: Import with PowerShell (Part 2 of 2)

Last Thursday I wrote about “Exporting SharePoint 2013 Design Packages with PowerShell”. Today I’d like to show you the import function. This functions can be used to handle with SharePoint 2013 Design Packages with PowerShell, e.g. in deployment scenarios. Therefore it should by very useful. (I hope so 😉 ) FEEDBACK WELCOME!!!

You can download the scripts here:

http://gallery.technet.microsoft.com/Export-and-Import-0f41b376

Here you can find the blog article about “Export-SPDesignPackage”: https://blog.kenaro.com/2013/02/14/sharepoint-2013-design-packages-export-with-powershell-part-1-of-2

 

The import function is called “Import-SPDesignPackage” and here are the details:

Import-SPDesignPackage

Here are some samples

#First sample

Import-SPDesignPackage -SiteUrl "http://sharepoint.local/publishing" -ImportFileName "C:\temp\publishing2.wsp" -PackageName "P2" -Apply $true

#Second sample


(
    @{ SiteUrl ="http://sharepoint.local/sites/publishing1";
       ImportFileName ="C:\temp\publishing1.wsp";
       PackageName ="P1";
       Apply=$true
    },
    @{ SiteUrl ="http://sharepoint.local/sites/publishing2";
       ImportFileName ="C:\temp\publishing2.wsp";
       PackageName ="P2";
       Apply=$true
    }
) | New-ObjectFromHashtable | Import-SPDesignPackage

The first sample shows you how to import one design package to a dedicated site. By using the “Apply” parameter the design package will be applied to the site immediately.

The second sample shows you hot to import two different packages to two different site collections. In the sample I use a hashtable for input parameters. They are assigned to the function parameters by “property name binding”. See “http://technet.microsoft.com/en-us/library/hh847743.aspx”: Section “ValueFromPipelineByPropertyName”:

[…]
For example, if the function has a ComputerName parameter, and the 
piped object has a ComputerName property, the value of the ComputerName
property is assigned to the ComputerName parameter of the function.

The following example declares a ComputerName parameter that is 
mandatory and accepts input from the ComputerName property of the 
object that is passed to the function through the pipeline.
[…]

Some more details about that. Skip it if you are not interested…

 

<begin/>

 

This “property name binding” does not work with hashtables. Therefore I created a helper function “New-ObjectFromHashtable” that creates a PowerShell object (“PSObject”). This function is generic. (It’s also included in the script files.)

 

On the one hand with “new-object System.Management.Automation.PSObject” you can create a new “empty” PowerShell object that can be used in your script as every other object, e.g. a SharePoint object like an instance of class SPSite. With cmdlet “Add-Member” you can add new members to the object. – On the other hand you have a hashtable with named values. You can access the collection of names = keys and with each key you can access the value. – Let’s combine it: You can iterate through the keys collection and create a new member in an empty PSObject instance.

 

functionNew-ObjectFromHashtable {
    #written by Ingo Karstein (https://blog.kenaro.com)# v1.0#Use this function to convert a hashtable to a PowerShell object ("PSObject"), e.g. for using hashtables for property name binding in# PowerShell pipelines
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
        [Hashtable]
        $Hashtable
    )

    begin {
        $results= @()
    }

    process {
        $r=new-objectSystem.Management.Automation.PSObject$Hashtable.Keys | % {
            $key=$_$value=$Hashtable[$key]
            $r | Add-Member-MemberTypeNoteProperty-Name$key-Value$value-Force
        }

        $results+=$r
    }

    end {
        $results
    }

}

The resulting object can be passed to each “property name binding” enabled cmdlet. – The PowerShell engine tries to match input object property names and cmdlet parameter names. If there is a match the input object property value gets assigned to the cmdlets input parameter.

 

The cmdlet can also convert a list of hashtables to a list of objects. That is used in the “Import-SPDesignPackage” script.

 

<end/>

Parameters

Parameter Name Parameter Set Name Mandatory? Position Description
SiteUrl Default Yes 0 Site Url for import
Site Site Yes 0 SPSite object for import
ImportFileName DefaultSite Yes 1 Filename and path of the design package for import
Apply DefaultSite Yes 2 $true = Apply the design package after import$false = Only install the design package for later activation
PackageName DefaultSite No 3 Package name. If not specified it uses the file name without extension. The package name will be used for naming the imported file in the solution gallery of the site collection
MajorVersion DefaultSite No Version number of the design package. If not specified it uses “1” for the major version.
MinorVersion DefaultSite No Version number of the design package. If not specified it uses “0” for the minor version.

 

The function returns an object for each processed (or not processed) site collection:

image

Object Property Description
SiteUrl Url of the processed site
Success $true = Import and “Apply” (if specified) was successful
InputFileFound $true = File found$false = File not found
InputFileExtensionValid $true = Input file has extension “.wsp”$false = Input file hat not extension “.wsp”
SiteFound $true = The specified site was found
SolutionFileName The name of the solution is auto generated from package name or file name and major and minor version number. This is the name of the package in the site collections solution gallery.
PackageAlreadyExsits $true = the solution does already exist in the solution gallery.

Some additions

The import process requires the package to be stored inside the site collection before the the last input step. Therefore the function creates a folder named “tmp_importspdesignpackage_15494B80-89A0-44FF-BA6C-208CB6A053D0” in the site collections root web root folder. In this folder the package gets uploaded. From the location the package is imported. The folder will be deleted after successful or not failed import.

SharePoint 2013 Design Packages: Export with PowerShell (Part 1 of 2)

Last night I search for “export import sharepoint 2013 design packages powershell”. No luck there. – So I created two functions for this purpose. It works nicely 🙂

The functions only use public methods of the SharePoint (Server) Object Model. No reflection, et cetera. The most important class in this context is “Microsoft.SharePoint.Publishing.DesignPackage”. It has two methods: Export and Install. This I used to create to PowerShell functions: Export-SPDesignPackage and Import-SPDesignPackage. Both are able to work in PowerShell pipeline context. This gives you the possibility to export a bunch of Design Packages from several sites using PowerShell.

You can download the scripts here:

http://gallery.technet.microsoft.com/Export-and-Import-0f41b376

You can use the scripts as PowerShell modules or by copying the content to your own PowerShell script files. But please be careful: By now it’s only tested in my dev environment!!

The scripts use the SharePoint Server Object Model. So they have to be executed on a SharePoint farm server! You also need an priviledged account that has rights to export (or import) SharePoint Design Packages.

Today I’ll describe the export function. Tomorrow or the day after tomorrow I’ll publish a description of the import function. Here is the import part: https://blog.kenaro.com/2013/02/18/sharepoint-2013-design-packages-import-with-powershell-part-2-of-2/

Export-SPDesignPackage

Here are some samples:

$site1=get-spsite"http://sharepoint.local/publishing" 
$site2=get-spsite"http://sharepoint.local/sites/publishing2"

 

#First Sample

 

$cred=new-objectSystem.Management.Automation.PSCredential( "domain\spfarm", (ConvertTo-SecureString-AsPlainText"Passw0rd"-Force))

$site1, $site2 | Export-SPDesignPackage -UseTempFileForExportWithExtension ".wsp" -DownloadCredentials $cred -PackageName "test"#Second Sample

 

$site1, $site2 | Export-SPDesignPackage -ExportFileName "C:\temp\Package.wsp" -UseExportFileNumbering -IncludeSearchConfig -DisposeSiteObject -OverwriteExistingFiles

#Third Sample

 

(
    @{PackageName="P1"; ExportFileName="C:\temp\p1.wsp"; SiteUrl="http://sharepoint.local/publishing"},
    @{PackageName="P2"; ExportFileName="C:\temp\p2.wsp"; SiteUrl="http://sharepoint.local/sites/publishing2"}
) | New-ObjectFromHashtable | Export-SPDesignPackage

 

#Fourth Sample

 

$site2 | Export-SPDesignPackage -ExportFileName "C:\temp\publishing2.wsp"  -IncludeSearchConfig -DisposeSiteObject -OverwriteExistingFiles

The first sample uses two SPSite objects as (pipeline) input and tells the function to export the design packages to temp files with auto generated names. For the download of the packages from the sites you can specify “download credentials”.

The second sample exports the same two SPSites. The export file name is specified. By using “UseExportFileNumbering” a number is inserted into the file name like this: “c:\temp\package-1.wsp”. So both export packages have different file names.

The third sample exports two sites  with individual export settings for each site. This is possible by “parameter binding by property name” where PowerShell binds the input object’s property to the function / cmdlet input parameters by name matching. But this does not work with hashtables. Therefore I created a helper function “New-ObjectFromHashtable” that creates a PowerShell object (“PSObject”). This function is generic. (It’s also included in the script files.)

The fourth sample exports just one site.

Parameters

The function has the following parameters. All can be bound by “parameter binding by property name”!

Parameter Name Parameter Set Name Mandatory? Position Description
SiteUrl Default Yes 0 Url of Site Collection as System.String
Site Site Yes 0 SPSite-object
ExportFileName Default

Site

No 1 Name for the exported file in the file system. The folder must exist!

Cannot be used together with “ExportFolder” and “UseTempFileForExportWithExtension”!

ExportFolder Default

Site

No 1 Name of the folder for the exported design packages. The file name will be created through the SharePoint Server Object Model.

Cannot be used together with “ExportFileName” and “UseTempFileForExportWithExtension”!

UseTempFileForExportWithExtension Default

Site

No 1 When specified the function will create a file name automatically by using "System.IO.Path.GetTempFileName()”. But it adds the extension you specify here. You should use “.wsp” by default.
PackageName Default

Site

No 2 Name of the export package. This is not a file name but an internal name that is used inside the design package. This is optional. The Object Model can handle it for you.
IncludeSearchConfig Default

Site

No 3 http://msdn.microsoft.com/en-us/library/jj862342.aspx
DisposeSiteObject Site No If you use SPSite objects as input you can specify whether the function should dispose it or not. Default: TRUE = “Please dispose it for me”!
DownloadCredentials Default

Site

No You can specify credentials for the download of the generated package. The package is stored in the the solution gallery of the site collection. It’s downloaded by the function by using System.Net.WebClient with “DefaultNetworkCredentials” if no credentials are specified.
OverwriteExistingFiles Default

Site

No If specified it overwrites the export file if it exists. If not specified it skips the export. But in the case nevertheless the design package will be created .
UseExportFileNumbering Default

Site

No If you use “ExportFileName” you can specify this parameter to insert numbers into the given file name as described above.

 

The function returns an object for each processed (or not processed) site collection:

image

This objects can be used in pipelines or … as you like.

Object Property Description
Site Url Url of the processed site
Success TRUE = Export successful, export file created.
SiteFound The site collection was found
ExportError $null = OK

System.Exception = Error occurred

PackageFileName Name of the Package created by the Object Model
PackageName Name of the Package created by you OR the Object Model
PackageMajorVersion …created by Object Model during export
PackageMinorVersion …created by Object Model during export
ExportFileOverridden If “TRUE” the export file did exist but was overridden during export. If “OverwriteExistingFiles” parameter was NOT specified this property will always be FALSE.
DownloadError $null = OK

System.Exception = Error during download

 

Some Additions

The design package will be stored in the solution gallery of the site collection that can be found here: <site-collection-url>/_catalogs/solutions. It’s the same location as for the sandboxed solutions. It interesting: sandboxed solutions are deprecated but in this case they use at least the same storage for a new functionality.

After the package is created you can download it from the solution gallery. No big deal.

Short Note About Error While Creating Search Service Application for SharePoint 2013 by PowerShell: “Value cannot be null. Parameter name: indexLocation”

Today I got an error while creating a Search Service Application for SharePoint 2013:

PS C:\> $SearchSA = New-SPEnterpriseSearchServiceApplication –Name “Enterprise Search Service Application” –ApplicationPool “Search App Pool” –DatabaseName “Search”

New-SPEnterpriseSearchServiceApplication : Value cannot be null.
Parameter name: indexLocation

To resolve this I just started the Search Service Instance on each (search) server in the farm and set it’s “DefaultIndexLocation” property.

After that I could create the Search Service App.

PS snippet:

"SearchServer1", "SearchServer2" | % {
    $svcInst = (Get-SPServer -Identity $_).serviceinstances | ? { $_.GetType().FullName -eq "Microsoft.Office.Server.Search.Administration.SearchServiceInstance" }
    $svcInst.DefaultIndexLocation = $defaultIndexLocation
    $svcInst.Update()
    $svcInst.Provision()
}

This did it.

PowerShell Script to Add Account to “Allow Logon Locally” privilege on Local Security Policy

As you know the SharePoint Farm Account must have privileges to logon locally for getting “User Profile Service Application” to work.

Today I created a PowerShell script that adds the given account to the “Allog Logon Locally” privilege in the Local Security Policy.

1. My account is “DOMAINsp_farm”

2. I start “secpol.msc” (“Local Security Policy”) on the local farm server

image

3. I’m looking for “Allow Logon Locally”. The account “sp_farm” is not in this setting.

image

4. I execute the script to add the account.

image

5. Then I reload the “Local Security Policy” or close and reopen the MMC.

image

6. Now the account in in the setting:

image

You can download the script here:

http://gallery.technet.microsoft.com/PowerShell-script-to-add-b005e0f6

This is the script:

image

BTW: A little trick for development in Visual Studio 2010 – Start PowerShell scripts that need to run in an x64 context by double click from Solution Explorer

For development and some related tasks I use PowerShell, of course. – I store the PowerShell scripts in my projects as part of them. They are part of the source code and they are saved in the source control.

If I need to run such a script I start it directly from Solution Explorer in VS2010.

Therefore I’ve set the default “open with…” to “powershell.exe”

image

image

image

If you have done this you can run every “.ps1” script file by double click in Solution Explorer!!

BUT… VS2010 itself is 32bit! – If you start an architecture independend process like “poweshell.exe” from within VS2010 it runs in 32bit environment! But sometimes you need to run a 64 version of PowerShell, e.g. for some SharePoint tasks that need an 64 bit environment.

Therefore I’ve created a little App “StartPS64”:

1. In VS2010 create a new Project of type “Console Application”

2. Open the project’s properties, select the “Build” tab and change “Platform target” to x64.

SNAGHTML57d9e4d

3. Edit “program.cs” and insert this code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace StartPS64
{
    class Program
    {
        static void Main(string[] args)
        {
            string fullname = System.IO.Path.GetFullPath(args[0]);
            if( !System.IO.File.Exists(fullname) )
            {
                Console.WriteLine("Script does not exist!");
                Console.ReadKey();
                return;
            }

            Process.Start(@"c:windowssystem32windowspowershellv1.0powershell.exe", fullname);
        }
    }
}

4. Compile it.

5. Specify the build “startps64.exe” as new “default application” for “.ps1” files in VS2010 as described above.

6. Now every “.ps1” file started from the Solution Explorer will run in an 64 bit environment and can execute SharePoint cmdlets!

PowerShell Tool to Enumerate the Content Type Usage in SharePoint 2010

A customer of mine has the problem that he wants to remove a Web Content Type but SharePoint says the content type is still in use.

Therefore I created a tool for enumerating all web and list content types of all sites.

The following script scans the SharePoint farm and creates a Windows form with a Treeview like this:

image

(Web) = Web Content Types

(List) = List Content Types

The tool show the content type inheritence:

image

Here you see the List Content Type “Image” that based on the Web Content Type “Image” that based on the Web Content Type “Rich Media Asset” that based on Web Content Type “Document” that is inherited from the “System” Content Type.

It’s possible to open the content type management site by clicking “_open management site” or open the related list or web by clicking “_open list” or “_open web”.

This is the script:

#region Init
    Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")  | Out-Null
    cls
#endregion

#region Form
    $form = New-Object System.Windows.Forms.Form

    $button = New-Object System.Windows.Forms.Button
    $treeview = New-Object System.Windows.Forms.TreeView

    $form.SuspendLayout()

    $button.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
    $button.Dock = [System.Windows.Forms.DockStyle]::Bottom
    $button.Location = (new-object System.Drawing.Point(0, 250))
    $button.Name = "button1"
    $button.Size = (new-object System.Drawing.Size(292, 23))
    $button.TabIndex = 0
    $button.Text = "Close"
    $button.UseVisualStyleBackColor = $true

    $treeview.Dock = [System.Windows.Forms.DockStyle]::Fill
    $treeview.Location = (new-object System.Drawing.Point(0, 0))
    $treeview.Name = "treeView1"
    $treeview.Size = (new-object System.Drawing.Size(292, 250))
    $treeview.TabIndex = 1

    $form.AutoScaleDimensions = new-object System.Drawing.SizeF(6.0, 13.0)
    $form.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Font
    $form.ClientSize = (new-object System.Drawing.Size(292, 273))
    $form.Controls.Add($button)
    $form.Controls.Add($treeview)
    $form.Name = "Content Type Usage "
    $form.Text = "Content Type Usage / https://blog.kenaro.com"
    $form.ResumeLayout($false)
#endregion

$list = @{}

$treeview.add_NodeMouseDoubleClick({
    param($sender, $eventArgs)
    if($eventArgs.Node.Tag -ne $null ) {
        [System.Diagnostics.Process]::Start($eventArgs.Node.Tag)
    }
});

function analyseWeb {
    param([Microsoft.SharePoint.SPWeb]$web) 
    
    $web.ContentTypes | % {
        $ct = $_
        $global:list += @{"$($ct.Id)"=$ct}
    }
    
    $web.lists | % {
        $_.ContentTypes | % {
            $ct = $_
            $global:list += @{"$($ct.Id)"=$ct}
        }
    }
    
    $web.Webs | % {
        analyseWeb -web $_
    }
}

function reorder {
    param([Microsoft.SharePoint.SPContentType]$p = $null, [int]$intend, [System.Windows.Forms.TreeNode]$parentNode)
    
    if( $p -eq $null ) {
        $ct = $list["0x"]

        $tn = New-Object System.Windows.Forms.TreeNode("System")
        $tn.ToolTipText = "0x"
        $parentNode.Nodes.Add($tn) | Out-Null
        
        reorder -p $ct -intend 0 -parentNode $tn
    } else {
        ($list.Values | ? {$_.Id.IsChildOf($p.Id) -and $_ -ne $p} | sort @{Expression={$_.Id.ToString().Length}}, $_.Id.ToString() ) | % {
            $ct = $_
            $type=""
            $url=""
            $tn = $null

            if( $ct.ParentList -ne $null ) {
                $type="(List) $($ct.Name) / List: $($ct.ParentList.Title) / Id: $($ct.Id.ToString())"            
                $url = $ct.parentweb.url + "/_layouts/ManageContentType.aspx?ctype=" + $ct.Id.ToString()+"&List="+$ct.ParentList.Id.ToString()
                
                #http://<domain>/sites/<web>/_layouts/ManageContentType.aspx?List=%7B111963EB%2DF504%2D49EC%2DA21B%2D814319718684%7D&ctype=0x010100644EE9F5F657474B92A2716207BB5DE2
                
                $tn = New-Object System.Windows.Forms.TreeNode($type)
                $tn1 = New-Object System.Windows.Forms.TreeNode("_open management site")
                $tn1.Tag = $url
                $tn.Nodes.Add($tn1) | Out-Null
                
                $tn2 = New-Object System.Windows.Forms.TreeNode("_open list")
                $tn2.Tag = $ct.ParentWeb.Site.MakeFullUrl($ct.ParentList.DefaultViewUrl)
                $tn.Nodes.Add($tn2) | Out-Null
            }else {
                $type="(Web) $($ct.Name) / Id: $($ct.Id.ToString())"
                $url = $ct.parentweb.url + "/_layouts/ManageContentType.aspx?ctype=" + $ct.Id.ToString()
            
                #http://<domain>/sites/<web>/_layouts/ManageContentType.aspx?ctype=0x0100CF8FCF53E9FA451CB145093F830B1762
                $tn = New-Object System.Windows.Forms.TreeNode($type)
                $tn1 = New-Object System.Windows.Forms.TreeNode("_open management site")
                $tn1.Tag = $url

                $tn.Nodes.Add($tn1) | Out-Null
                $tn2 = New-Object System.Windows.Forms.TreeNode("_open web")
                $tn2.Tag = $ct.ParentWeb.Url
                $tn.Nodes.Add($tn2) | Out-Null
            }

            $parentNode.Nodes.Add($tn) | Out-Null
        
            reorder -p $ct -intend ($intend+1) -parentNode $tn
        }
    }
}

if($fileName -ne $null ) { 
    Remove-Item -Path $fileName -Confirm:$false -ErrorAction SilentlyContinue
}

#you can filter the processed site collections in the following line
Get-SPSite -Limit all <#| select -first 1 -Skip 10#> | % {
    $site = $_
    $tn = New-Object System.Windows.Forms.TreeNode("Site: $($site.Url)")
    $treeview.Nodes.Add($tn)
    
    $rootWeb = $site.RootWeb
    
    analyseWeb -web $rootWeb
    
    reorder -parentNode $tn
    
    $rootWeb.Dispose()
    $site.Dispose()
}

$form.ShowDialog() | out-null

“SPWebPSConsole”: Web based PowerShell Console for SharePoint (A new project on Codeplex)

This project is a stand-alone clone of my project „Web based PowerShell Console” released on 2011-09-02 on Codeplex and in this blog article:

https://blog.kenaro.com/2011/09/02/webpsconsole-web-based-powershell-console-a-new-project-on-codeplex/

I’ve adapted the project to run in the SharePoint 2010 Central Administration.

The project is on Codeplex: http://spwebpsconsole.codeplex.com

To install the project just by deploying the solution. The SharePoint feature will automatically be activated in the Central Administration Web Application without need of further actions.

In the “System Settings” section of the CA you’ll find a new link:

image

Just click it and you will get this:

image

image

The SharePoint PowerShell SnapIn is loaded automatically so that you can start immediately to execute SharePoint Cmdlets!

Here is an example:

“Get-SPSite”

image

Or this one:

image

Request for value for missing parameter “Path” of Cmdlet “Backup-SPSite”

image

Final output:

image

The project is “BETA”. I need your help to improve the project. Please feel free to test it or to extend it. But if you do so please send me your results!

Have fun!

“WebPSConsole”: Web based PowerShell Console (A new project on Codeplex)

Starting at 12th of September 2011 I’ll be freelancer. – In August I had some vacation days to spend. – So I created a new project for me – and for you.

I call it “Web based PowerShell Console” or “WebPSConsole”.

It is a full featured Browser based PowerShell console that enables you to work on a server machine remotely. It’s like PowerShell remoting but it’s not the same. While PowerShell remoting uses “WinRM” the tool that I’ve created uses a “normal” PowerShell host that will be executed in an IIS environment. (Please see my comment related to “PowerGUI Pro Mobile Shell” at the end of this article.)

That means: With my tool you will have a ASP.NET 4.0 based Web Application that can be accessed by a browser. The ASP.NET web app has a .ASPX site and some code behind. On the server in the ASP.NET context a PowerShell Host developed by me is running that accepts commands send by the clients Browser. If a command is send it will be executed by the PowerShell Host. All output is send back to the clients Browser.

It’s on Codeplex: http://webpsconsole.codeplex.com with all source code! – It’s ALPHA. I’ve done lots of testing but I’m a single developer with a single machine… I’d be happy to get your improvements or experiences!

Lets have a look at the app:

image

That is the “GUI”. The black frame will contain the output of the server side PowerShell session.

Let’s enter a command.

get-childitem c:windows | select -first 5

image 

After clicking “Send” the command is send to the server. The server executes the command and returns the PowerShell output.

Let’s try this:

get-credential

image 

You see the blue colored input box for the credential information input.

Here you see the implemented “session timeout”:

image

After 15 minutes of inactivity you’ll get a warning. After 20 minutes the session will be terminated. After termination you are not able to access the old session including session history!

Let’s try this:

a$ = read-host

image 

Here you get an input field for a single line of text. The output is stored in variable $a. Let’s check the content of variable $a after sending the input:

image 

Now let’s test the functionality of completing missing cmdlet parameters:

get-content

image 

image

Leave the last line empty and click “Send”.

image

You get:

image

Now let’s test the “choice” functionality:

image 

After “Send” you get:

image 

You see here the blue colored choice input field. If you choose “Halt Command” in this case the “Hello Ingo” command will not be executed:

image 

If you select “Cancel” in such cases the complete PowerShell pipeline will be stopped! Not only the current command!

You can set colors a you need to:

image 

Than “Send” and test it with:

image 

You’ll get:

image

Now let’s have a look at the function “Download console as RTF”. You’ll find the link above the console frame.

image 

Click “Open”.

image 

This is a Rich Text Format copy of the complete console output! – A “Clear-Host” will not clear this output! – This file can only be downloaded while the current session running. After the session ends you will not be able to access the information anymore!

Let’s try “Show current buffer”. This command is above the console frame too.

image 

Here you get the “real” PowerShell buffer as HTML. This page can be save.

Let’s try:

clear-host

image 

Open “Show current buffer” again:

image 

(It’s empty now because clear-host did clear the PowerShell buffer!)

A word to the keyboard usability:

In the command input field you can use ESCAPE to clear the input field.

You can use CTRL+ENTER to send the command.

In other input fields (choice, credential, read-line,…) except “Read-Key” (see below) you can use ESCAPE to cancel the current operation and pipeline.

You can use there ENTER to send your entered data.

You can use TAB and SHIFT+TAB to navigate inside the input frame: input field, Send button and Cancel button.

The “Trace log” is an optional frame that maybe shows more information about your server connection and the “work behind”. Just click the line and the frame will be shown:

image 

(“Send buffer as HTML” is caused by the “Show current buffer” function.)

You can close the PowerShell session by clicking the “x” in the dialog title (beside “v0.1.0.0”):

image 

Now you can close the Browser or click “Ok” to start a new session. Or reload the Browser page to create a new session.


Setup

Now I want to tell you how to install the project.

First of all you need IIS. This can be installed on Windows 7 too.

You need a user account for executing PowerShell at server side. This account is used by every user of WebPSConsole: Each user of WebPSConsole will be impersonated at the server with the “execution account”. Here you should choose the account very carefully.

1.

Create a directory on the server where you store the binaries. You create a folder “c:inetputwebpsconsole”.

image 

 

2.

Copy the binaries there.

image 

 

3.

Open IIS Manager.

Create a new Application Pool for the “execution account”. This account need to execute ASP.NET 4.0 code.

image 

Select “Classical Mode” for the application pool! – Please check the settings. In my case the settings were not used. After creating the app pool I had to edit it and set “Pipeline Mode” and “Framework Version” again!!!

 

4.

Create a new web application “WebPSConsole” using the previously created application pool.

Right click the “Sites” node in IIS Manager.

image 

You get this dialog. Fill in your specific informations. Here is my sample. I’ll use “ikWebPSConsole” as host header name in this setup demo because the host header “webpsconsole” already exists for development purpose.

image 

Click the “Select” button beside “Application pool:

image 

“OK”.

My sample data:

image 

(In order to get this working on my machine I have to edit c:windowssystem32driveretchosts and insert “ikWebPSConsole” as new local DNS entry.)

 

5.

Change the Authentication settings. Disable “Anonymous Access” and enable “Windows Authentication”.

Select the web app in the treeview. On the right side double click “Authentication”.

image 

You get:

image 

First double click “Anonymous Authentication” and deactivate it!

image 

Now enable “Windows Authentication” the same way!

 

6.

You should restrict the usage of the web app by setting access restrictions.

image 

There you can grant or deny access to the web app for specific users, e.g. administrators.

As an example: Here only “DOMAINAdministrator” will have access to WebPSConsole.

image 

You should enable SSL.

I’ve done this already in the setup example above while creating the web app.

You could use “SSL client certificates” to protect the web app.

7. Test the app.

Security

  • I’ts as secure as you configure it!
  • You can restrict the rights of the console on the server by selecting a carefully configured user account as application pool account. You don’t have to configure this user to be “local admin” 😉
  • You can use SSL! (As shown above)
  • You can use Client-SSL-Certificates!
  • You can restrict client IP addresses!
  • You can restrict access by secifying users (as shown above).
  • BUT be sure you know what you do – as always 😉

Limitations

  • It’s not as fast as a local PowerShell because of the client-server-interaction.
  • Currently it runs only under the user context of the application pool. There should be “real” impersonation of the logged in user. But this did not work.
  • It consumes memory on the server because of the data saved on the server in the session. Be sure to monitor the servers memory usage if you deploy it on live servers!
  • There is not “Progress” support at the moment!

 

Ideas for future features:

  • Command completion (like “Intellisence”)
  • Client side syntax check
  • “Color code”

Please help me to impove the app! – Please post comment of your experiences!


Have fun!


For this project I’ve used:

 

 

A word to PowerGUI Pro MobileShell

As I said I’ve developed this project in my last vacation. After finishing v0.1.0.0 I’ve seen PowerGUI Pro MobileShell by Quest Software
. It’s based on the same idea as my project but is older and has more features I think. I only know a YouTube video of it because I do not know the commercial version of PowerGUI. – Beside this the free PowerGUI tool is my favorite PowerShell IDE. – For use on a live server you should think about using PowerGUI Pro MobileShell!