SharePoint PnP: get all sites and subsites in tenant / web app

I wanted to use SharePoint PnP to get all the sites and subsites in a web application (on-premises).  I decided to use search, similar to how a user named Unnie solved it here with CSOM.

Script code

$creds = "MyPnPScriptConnectionCredentials"; # <-- arbitrary name
$url = "https://webappofinterest.domain.com"; # <-- SP on-prem web app of interest

$cnx = Connect-PnPOnline `
    -Credentials $creds `
    -UseAdfs `
    -Url $url `
    -ReturnConnection;

$qry = Submit-PnPSearchQuery `
    -Connection $cnx `
    -Query "Path:$url AND ((ContentClass:STS_Site) OR (ContentClass:STS_Web))" `
    -All `
    -TrimDuplicates:$false;

$sites = $qry.ResultRows | % { $_.Path } | Sort-Object;
$sites;

Setup steps

  1. Installed SharePointPnPPowerShell{2013|2016|2019} while logged in as the user account that the script runs under.  (I downloaded PnP from here.)
  2. Cached one set of credentials in Credential Manager in Windows so that I could run the script without saving sensitive info in there…  Went to Control Panel – User Accounts – Credential Manager.  Scrolled to the Generic Credentials section.  Added generic credentials as follows:
    • Internet or network address = MyPnPScriptConnectionCredentials
    • User name = DOMAIN\FarmSearchCrawlAccount
    • Password = {password for farm search crawler account}
  3. Made sure that the farm search crawler account was the default content access account in the Search Service Application, which is found on the Search Administration page under System Status in Central Admin.

Issues seen and solved

I encountered errors about “collection has not been initialized”, “it may need to be explicitly requested”, etc.  To solve this, I had to:

  1. Assign the result of the PnP commands to PowerShell variables.
  2. Use the correct PnP distribution to match the SharePoint on-premises version.  (I had thought that I could use 2019 for 2019, 2016 and 2013, for instance, but that resulted in inexplicable nonsensical errors.)

Footnote

In SharePoint Online PnP, there exists a Get-PnPSubWebs cmdlet that is part of the SharePointPnPPowerShellOnline distribution.

Prickly pear pads – MCSS plant sale – 10th & 11th of September 2016

For folks who have purchased some pads of the prickly pears from me at the annual show and sale, here are some photos of some of these plants in the garden and in nature, and also a link to the USDA plant profile.

Opuntia fragilis:

http://plants.usda.gov/core/profile?symbol=OPFR

OF1 - 2011-MAY - Opuntia fragilis "HF" [Minnesota] - (Michigan)

Opuntia humifusa:

http://plants.usda.gov/core/profile?symbol=OPHU

Opuntia humifusa (Raf.) Raf. (Cactaceae) - Prickly pear - (Michigan)

Opuntia phaeacantha:

http://plants.usda.gov/core/profile?symbol=OPPH

OPh1 - 2011-JUN - 17001-(1) Opuntia phaeacantha DJF970.18 [Canada] - (Michigan)

From the flyer:

Prickly pear cacti may be offered as pads, and that’s what these are. This cactus needs to be planted in a certain way: it needs to be rooted. I root my prickly pear cactus by laying the pad on the ground the way a dish sits on a table, but a little more on an angle (like a saucer or plate tipped slightly on one edge). The edge that I tip toward the ground is always the base or narrowest part of the pad, where the pad was cut off the main plant. I ensure that edge tipped down to the ground has good contact with the ground (soil, not rocks) and that it is never allowed to remain damp or soggy for any length of time. I never bury the pads much. Finally, I ensure that the pads don’t grow legs and walk off — I keep the part tipped to the ground using a wire coat hanger bent in a U-shape and stuck in the ground.
Well-drained soil is what these cacti are used to. These particular cacti are growing outdoors in sandy loam, sand + gravel, or potting soil + lots of perlite. They are grown in the full sun (south and west sides of the house) and not anywhere where water can pool, nor where the soil is soggy.
I plant them anywhere between April and early October. On occasion, I have stored some of my prickly pear pads indoors in a dry, cool, dark place over the winter when I could not plant them by October. But all these pads for sale were freshly cut within the last two weeks.

Using CSOM with SAML Authentication (ADFS)

Topics: ADFS 3.0, SharePoint 2013, claims authentication, on-premise, Azure, CSOM, SAML

Description

Recently, I was tasked with making CSOM work with these SAML-enabled web applications and host-named site collections.  So I went to the great Google and Bing parts bins, found some things that I could build upon, and got to work.

My first iteration or attempt at making CSOM work with SAML was straightforward.  I found a couple of articles that described how to use a WebClient to pop the ADFS login dialog, which then set the security token that the CSOM ClientContext used.  Works great and it is not complicated.

Problem

But at about 3 a.m. one morning, I woke up with a thought: what if I wanted to do the same thing without prompting anybody, such as for running CSOM code as a scheduled task?  So I spent some evenings on the couch researching this, gathering useful “parts” from folks online, and writing additional code.  And Fiddler and Postman were my good friends, too.

The end result shown below is a functional console application that authenticates to ADFS, obtains a security token, extracts and repackages the FedAuth cookie, and uses the FedAuth cookie for the SharePoint CSOM ClientContext to do work against a SharePoint list.

In order to make this work, an intimate understanding of the ADFS configuration is needed.

Solution

Credit and thanks for certain sections of code go to the posts identified in the source code comments and bolded.  No wheels were reinvented in the making of this solution, only custom code.  Please visit those links highlighted in the source code below before examining this code.

Note that this code has a dependency on these NuGet packages, one of which delivers TokenHelper.cs and SharePointContext.cs.

<packages>
 <package id="AppForSharePointOnlineWebToolkit" version="3.1.2" targetFramework="net45" />
 <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
 <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
 <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
</packages>
using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using System.Web;
using System.Xml;
using SP = Microsoft.SharePoint.Client;

namespace MyConsoleApp
{
 class Program
 {
 private static string adfsEndpoint = "https://somesite.xxxx.loc/_trust/"; // Endpoint for site from where we get the token 
 private static string adfsRealm = "urn:somesite.xxxx.loc:somesite"; // ADFS realm that contains the endpoint above
 private static CookieContainer fedAuth = new CookieContainer();
 private static string webUrl = "https://somesite.xxxx.loc/finance";
 private static string fedAuthRequestCtx = HttpUtility.UrlEncode(string.Format("{0}/_layouts/15/Authenticate.aspx?Source=%2ffinance", webUrl));

 static void Main(string[] args)
 {
 // BEGIN adapted for C# from https://blogs.msdn.microsoft.com/besidethepoint/2012/10/17/request-adfs-security-token-with-powershell/

 string adfsBaseUri = "https://sts.xxxx.loc"; // your ADFS farm URL
 string adfsTrustEndpoint = "usernamemixed"; // OOTB ADFS, do not change
 string adfsTrustPath = "adfs/services/trust/13"; // OOTB ADFS, do not change
 string domain = "EUROPE"; // domain of AD account; should be in config file or propmted from the command line
 string pwd = "Quercus9%%"; // password of domain account; should be a secure string from config file, but you get the idea
 string userName = "jdoe@xxxx.loc:"; // UPN of domain account in AD

 EndpointAddress ep = new EndpointAddress(adfsBaseUri + "/" + adfsTrustPath + "/" + adfsTrustEndpoint);
 WS2007HttpBinding binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
 binding.Security.Message.EstablishSecurityContext = false;
 binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
 binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

 WSTrustChannelFactory wstcf = new WSTrustChannelFactory(binding, ep);
 wstcf.TrustVersion = TrustVersion.WSTrust13;
 NetworkCredential cred = new NetworkCredential(userName, pwd, domain);
 wstcf.Credentials.Windows.ClientCredential = cred;
 wstcf.Credentials.UserName.UserName = cred.UserName;
 wstcf.Credentials.UserName.Password = cred.Password;
 var channel = wstcf.CreateChannel();

 string[] tokenType = { "urn:oasis:names:tc:SAML:1.0:assertion", "urn:oasis:names:tc:SAML:2.0:assertion" };
 RequestSecurityToken rst = new RequestSecurityToken();
 rst.RequestType = RequestTypes.Issue;
 rst.AppliesTo = new EndpointReference(adfsRealm);
 rst.KeyType = KeyTypes.Bearer;
 rst.TokenType = tokenType[0]; // Use the first one because that is what SharePoint itself uses (as observed in Fiddler).

 RequestSecurityTokenResponse rstr = new RequestSecurityTokenResponse();
 var cbk = new System.Net.Security.RemoteCertificateValidationCallback(ValidateRemoteCertificate);
 ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; };
 SecurityToken token = null;
 try
 {
 token = channel.Issue(rst, out rstr);
 }
 finally
 {
 ServicePointManager.ServerCertificateValidationCallback = cbk;
 }

 // END adapted for C# from https://blogs.msdn.microsoft.com/besidethepoint/2012/10/17/request-adfs-security-token-with-powershell/

 // BEGIN adapted from https://github.com/OfficeDev/PnP-Sites-Core/blob/master/Core/SAML%20authentication.md

 Cookie fedAuthCookie = TransformTokenToFedAuth(((GenericXmlSecurityToken)token).TokenXml.OuterXml);
 fedAuth.Add(fedAuthCookie);

 // END adapted from https://github.com/OfficeDev/PnP-Sites-Core/blob/master/Core/SAML%20authentication.md

 // PERFORM CSOM OPS HERE:

 string listName = "Some Existing List";
 using (SP.ClientContext ctx = new SP.ClientContext(webUrl))
 {
 ctx.ExecutingWebRequest += ctx_ExecutingWebRequest;
 SP.CamlQuery query1 = new SP.CamlQuery();
 query1.ViewXml =
 "<View>" +
 "<Query>" +
 "<OrderBy>" +
 "<FieldRef Name='Title' Ascending='FALSE'/>" +
 "</OrderBy>" +
 "</Query>" +
 "<RowLimit>10</RowLimit>" +
 "</View>";
 SP.List list = ctx.Web.Lists.GetByTitle(listName);
 SP.ListItemCollection items = list.GetItems(query1);
 ctx.Load(items);
 ctx.ExecuteQuery();
 for (int i = 0; i < items.Count; i++)
 {
 SP.ListItem item = items[i];
 ctx.Load(item);
 ctx.ExecuteQuery();
 }
 }
 }

 static void ctx_ExecutingWebRequest(object sender, SP.WebRequestEventArgs e)
 {
 e.WebRequestExecutor.WebRequest.CookieContainer = fedAuth;
 }

 private static Cookie TransformTokenToFedAuth(string samlToken)
 {
 // Adapted from https://github.com/OfficeDev/PnP-Sites-Core/blob/master/Core/OfficeDevPnP.Core/IdentityModel/TokenProviders/ADFS/BaseProvider.cs

 samlToken = WrapInSoapMessage(samlToken, adfsRealm);
 string stringData = String.Format("wa=wsignin1.0&wctx={0}&wresult={1}", fedAuthRequestCtx, HttpUtility.UrlEncode(samlToken));
 HttpWebRequest req = HttpWebRequest.Create(adfsEndpoint) as HttpWebRequest;
 req.Method = "POST";
 req.ContentType = "application/x-www-form-urlencoded";
 req.CookieContainer = new CookieContainer();
 req.AllowAutoRedirect = false;
 Stream newStream = req.GetRequestStream();

 byte[] data = Encoding.UTF8.GetBytes(stringData);
 newStream.Write(data, 0, data.Length);
 newStream.Close();
 HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
 var encoding = ASCIIEncoding.ASCII;
 string responseText = "";
 using (var reader = new System.IO.StreamReader(resp.GetResponseStream(), encoding))
 {
 responseText = reader.ReadToEnd();
 }
 return resp.Cookies["FedAuth"];
 }

 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Xml.XmlDocument.CreateTextNode(System.String)")]
 private static string WrapInSoapMessage(string stsResponse, string relyingPartyIdentifier)
 {
 // This method code is unchanged in its entirety from https://github.com/OfficeDev/PnP-Sites-Core/blob/master/Core/OfficeDevPnP.Core/IdentityModel/TokenProviders/ADFS/BaseProvider.cs.
 // I added in two places some inline commented code, which deals with using SAML 2.0 XML schema instead of 1.x. But SharePoint doesn't use 2.0. 

 XmlDocument samlAssertion = new XmlDocument();
 samlAssertion.PreserveWhitespace = true;
 samlAssertion.LoadXml(stsResponse);

 //Select the book node with the matching attribute value.
 String notBefore = /*samlAssertion.DocumentElement["Conditions"]*/samlAssertion.DocumentElement.FirstChild.Attributes["NotBefore"].Value;
 String notOnOrAfter = /*samlAssertion.DocumentElement["Conditions"]*/samlAssertion.DocumentElement.FirstChild.Attributes["NotOnOrAfter"].Value;

 XmlDocument soapMessage = new XmlDocument();
 XmlElement soapEnvelope = soapMessage.CreateElement("t", "RequestSecurityTokenResponse", "http://schemas.xmlsoap.org/ws/2005/02/trust");
 soapMessage.AppendChild(soapEnvelope);
 XmlElement lifeTime = soapMessage.CreateElement("t", "Lifetime", soapMessage.DocumentElement.NamespaceURI);
 soapEnvelope.AppendChild(lifeTime);
 XmlElement created = soapMessage.CreateElement("wsu", "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
 XmlText createdValue = soapMessage.CreateTextNode(notBefore);
 created.AppendChild(createdValue);
 lifeTime.AppendChild(created);
 XmlElement expires = soapMessage.CreateElement("wsu", "Expires", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
 XmlText expiresValue = soapMessage.CreateTextNode(notOnOrAfter);
 expires.AppendChild(expiresValue);
 lifeTime.AppendChild(expires);
 XmlElement appliesTo = soapMessage.CreateElement("wsp", "AppliesTo", "http://schemas.xmlsoap.org/ws/2004/09/policy");
 soapEnvelope.AppendChild(appliesTo);
 XmlElement endPointReference = soapMessage.CreateElement("wsa", "EndpointReference", "http://www.w3.org/2005/08/addressing");
 appliesTo.AppendChild(endPointReference);
 XmlElement address = soapMessage.CreateElement("wsa", "Address", endPointReference.NamespaceURI);
 XmlText addressValue = soapMessage.CreateTextNode(relyingPartyIdentifier);
 address.AppendChild(addressValue);
 endPointReference.AppendChild(address);
 XmlElement requestedSecurityToken = soapMessage.CreateElement("t", "RequestedSecurityToken", soapMessage.DocumentElement.NamespaceURI);
 XmlNode samlToken = soapMessage.ImportNode(samlAssertion.DocumentElement, true);
 requestedSecurityToken.AppendChild(samlToken);
 soapEnvelope.AppendChild(requestedSecurityToken);
 XmlElement tokenType = soapMessage.CreateElement("t", "TokenType", soapMessage.DocumentElement.NamespaceURI);
 XmlText tokenTypeValue = soapMessage.CreateTextNode("urn:oasis:names:tc:SAML:1.0:assertion");
 tokenType.AppendChild(tokenTypeValue);
 soapEnvelope.AppendChild(tokenType);
 XmlElement requestType = soapMessage.CreateElement("t", "RequestType", soapMessage.DocumentElement.NamespaceURI);
 XmlText requestTypeValue = soapMessage.CreateTextNode("http://schemas.xmlsoap.org/ws/2005/02/trust/Issue");
 requestType.AppendChild(requestTypeValue);
 soapEnvelope.AppendChild(requestType);
 XmlElement keyType = soapMessage.CreateElement("t", "KeyType", soapMessage.DocumentElement.NamespaceURI);
 XmlText keyTypeValue = soapMessage.CreateTextNode("http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey");
 keyType.AppendChild(keyTypeValue);
 soapEnvelope.AppendChild(keyType);

 return soapMessage.OuterXml;
 }

 private static bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors)
 {
 // It may be insightful to examine the parameters here, while debugging, although the function only returns a value of true regardless of those parameters.
 return true;
 }
 }
}

Regular Expressions (RegEx) – Wildcard Pattern Find and Replace – Visual Studio

On a recent project where I used ILSpy and Reflector.NET to extract source code from an old DLL, I ended up with some thousands of slightly incorrect things.  So in order to make the code workable, I had to do a wildcard find of complex things and fix them using Find and Replace in Visual Studio.

I needed to do a wildcard replace of things like this:
.set_PropName(somevalue);

with a format like this:
.PropName = somevalue;

After experimenting, the following Find and Replace criteria worked.  (Make sure to select the little [.*] icon in the Find and Replace window, which tells Visual Studio 2013 or later to use regular expressions. Also, only ever just do a Find All before replacing even a single match until you tune your regex find statement exactly as you need it.)

Find box:
.set_(.[a-zA-Z0-9\-\"_]*)(\()(.[a-zA-Z0-9\-\"_]*)(\));

Replace box:
.$1 = $3;

What is all this madness?  Well, a $1, $2, $3, etc. in the Replace statement refers to each of the paren-delimited groups of info in the Find statement.  Regex find groups are delimited with ( and ).  So the first group is (.[a-zA-Z0-9\-\”_]*) and the third group is (.[a-zA-Z0-9\-\”_]*).

BEST PRACTICES:
Check code into the source repository first. Use Find All, not Replace All, in order to examine what is going to be touched by the replace operation. In the Find Results window, examine the found items one at a time for accuracy, since regular expression matching often finds more than is expected. Then replace only one or two items to see if the replace statement is also correct.

LOTS MORE EXAMPLES

To find uninitialized string variables, such as string myStringVar;, I placed this in the Find box:
string (.[a-zA-Z0-9_]*?)(\;)
To replace with initial value of string.Empty, such as string myStringVar = string.Empty;, I placed this in the Replace box:
string $1 = string.Empty;

To find unwanted extra closing paren, such as this.lblMyLabel.Text), I placed this in the Find box:
= this.lbl(.*?).Text\)
To replace, getting rid of unwanted unnecessary closing paren, such as this.lblMyLabel.Text, I placed this in the Replace box:
= this.lbl$1.Text

More examples are below. The first line goes in the Find box and the second line goes in the Replace box. I first opened open all my source files in the VS IDE or else VS Find and Replace – Entire Solution does not find everything all at once.

Change all instances of int somevariable; to int somevariable = 0;
int (.[a-zA-Z0-9_]*?)(\;)
int $1 = 0;

Change all instances of .get_SomePropName() to .SomePropName
.get_(.[a-zA-Z0-9_]*?)(\(\))
.$1

Change all instances of .ItemType == somenumber to .ItemType == (ListItemType)somenumber
.ItemType == (.[0-9]*?)
.ItemType == (ListItemType)$1

Change all instances of (SomeObject.PartialMethodNameRestOfTheMethodName to (QualifiedName.SomeObject.PartialMethodNameRestOfTheMethodName
\(SomeObject.(.[a-zA-Z0-9]*?)RestOfTheMethodName
(QualifiedName.SomeObject.$1RestOfTheMethodName

Change all instances of .get_Item(SomeKeyHere) to [SomeKeyHere]
.get_Item\((.[a-zA-Z0-9_]*?)(\))
[$1]

Change all instances of .get_Item("SomeKeyHere") to ["SomeKeyHere"]
.get_Item\((.[a-zA-Z0-9_\"]*?)(\))
[$1]

Comment out an entire paragraph of code, on multiple lines and with brackets:

To change this

IEnumerator _myvar_ref_0;
if (_myvar_ref_0 is IDisposable)
{
((IDisposable)_myvar_ref_0).Dispose();
}

Into this
// IEnumerator _myvar_ref_0; /* 2015-01 commented code */
// if (_myvar_ref_0 is IDisposable) /* 2015-01 commented code */
/*{
((IDisposable)_myvar_ref_0).Dispose();
}*/ /* 2015-01 commented code */

I created and used the following Find and Replace statements (in three sepearate Find-Replace operations)
Set 1
Find
IEnumerator _myvar(.[a-zA-Z0-9_]*?)(\;)
Replace
// IEnumerator _myvar$1; /* 2015-01 commented code */
Set 2
Find
(\*\/\r\n.[\t]*)if (\(_myvar)(.[a-zA-Z0-9_]*?)( is IDisposable\))(\r\n)
Replace
$1// if $2$3$4 /* 2015-01 commented code */$5
Set 3
Find
(\*/\r\n.[\t]*)({\r\n.[\t]*\(\(IDisposable\)_myvar)(.[a-zA-Z0-9_]*)(\)\.Dispose\(\)\;\r\n.[\t]*\})
Replace
$1/*$2$3$4*/ /* 2015-01 commented code */

SharePoint Client Object Model, CAML, JavaScript – Get Item by ID – Errors Solved

I came across something of a “stumper” when trying to get a list item in JavaScript (SP.CamlQuery) using the item’s ID.  It would not work.  I found many articles and forum entries that were only partially helpful in that they made me look at things to try for the first error shown below.  But as it turned out, nothing worked exactly as shown for other folks, as far as I could find.

Maybe this is specific to a certain version of the Client Object Model in SharePoint 2013, not sure.  My current version of the Microsoft.SharePoint.Client.* DLLs is v4.0.30319.  At any rate, the solution shown below worked for me.

PROBLEM

ERROR 1
One or more field types are not installed properly. Go to the list settings page to delete these fields.

ERROR 2
The property or field has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.

SOLUTION

So as it turned out, the only thing that would work for me was to use the following code (important parts in bold):

var query = new SP.CamlQuery();
query.set_viewXml(
“<View><Query><Where><And>” +
“<FieldRef Name=’ID’ LookupID=’True’ /><Value Type=’Counter’>” + currentId + “” +
“<FieldRef Name=’ContentTypeId’ />{some-contenttype-id-here-notimportant}” +
“</And></Where></Query></View>”);
listItems = myList.getItems(query);
ctxt.load(listItems, ‘Include(ID, Id, OtherField1, OtherField2, OtherFieldN)’);
ctxt.executeQueryAsync(mySuccessProc, myFailProc);

It would only work when the LookupID=’True’ was added, value type was Counter, and both ID and Id were in the Include() for ctxt.Load().

Not sure if this will apply to anybody else out there.  But if it helps, cool!

Windows RT store app dev – Inputs to / outputs from public and private async functions in C# and VB.NET

Windows RT (Windows 8.1) does not support exposing Task<T> on public methods and properties of class libraries (winmd files). As such, we are told to use IAsyncOperation<T> in place of Task<T>, since it provides similar functionality.

Here is an example of that in C#. The public function wraps the private one and thus allowed me to “await” my GetSomeData() function from another project that consumes this library.

private async Task<SomeOutput> GetSomeDataInternal(SomeInput inp)
{
SomeOutput ou = new SomeOutput();
using (var r = new StreamReader(await inp.OpenStreamForReadAsync()))
{
ou = await r.ReadToEndAsync();
}
return ou;
}

public IAsyncOperation<SomeOutput> GetSomeData(SomeInput inp)
{
return (IAsyncOperation<SomeOutput>)AsyncInfo.Run((System.Threading.CancellationToken ct) => GetSomeDataInternal(inp));
}

Calling the public function looks like this:

Dim myLib As MyLibrary.Utils = New MyLibrary.Utils()
Dim inp As New SomeInput()
Dim ou As SomeOutput = Await myLib.GetSomeData(inp)

All is well and good with a provider class library in C#. But that is not the case in pure VB.NET. I much prefer C# for complex things, but one must travel to where the river flows. So this had to be in VB.NET. The problem: when I attempted to do something similar to the above entirely in VB.NET, I encountered a COM error. So in order to make everything work, I devised the following code.

(Disregard that the output is gotten by accessing a property instead of calling a function; the main thing here is how to wrap the async private function in a public one and call it without COM errors and typecasting errors.)

Private _inp As SomeInput()
Public Property TheInput As SomeInput
Get
Return Me._inp
End Get
Set(value As SomeInput)
Me._inp = value
End Set
End Property

Public ReadOnly Property TheOutput As IAsyncOperation(Of SomeOutput)
Get
Return AsyncInfo.Run(Of SomeOutput)(Function(ct As System.Threading.CancellationToken) GetTheOutput())
End Get
End Property

Private Async Function GetTheOutput() As Task(Of SomeOutput)
Dim s As StorageFile = Await Windows.Storage.KnownFolders.PicturesLibrary.GetFileAsync(Me._inp)
Dim ou As New SomeOutput()
Await ou.SetSourceAsync(Await s.OpenReadAsync())
Return ou
End Function

So the above wrapped code was necessary to make Windows RT happy, since Task<T> may not be exposed publicly.
And I called the function (or in this case got the property) like so:

Dim myClass As New MyClass()
Dim inp As New SomeInput()
myClass.TheInput = inp

Dim ou As SomeOutput = Await myClass.TheOutput.AsTask()

AsTask() was necessary to cast the IAsyncOperation<T> back to Task<T> and make VB.NET happy.

So there are several things mentioned here:
– AsyncInfo.Run() in VB.NET
– Disentangling data from an async function in C# and VB.NET
– Avoiding the COM error
– Avoiding the typecasting error

Pollination and hybridization of yucca species

Yuccas are pollinated by the yucca moth. A nice introduction to this is given in this article: http://www.fs.fed.us/wildflowers/pollinators/pollinator-of-the-month/yucca_moths.shtml.

Just as there are many species of yucca, there are three genera and many species of the yucca moth. One genus, Tegeticula, is shown in this article with twenty species: http://en.wikipedia.org/wiki/Tegeticula. Another genus is Parategeticula. And there is a genus of false yucca moths, Prodoxus: http://pdfsb.com/yucca+moth+manual.

Side bar: In my own garden in Michigan I have many Yucca filamentosa, which are pollinated by the Tegeticula yuccasella. Y. filamentosa is classified as native or naturalized here, depending on what source of information you are reading. In any case, every year there is a period of intense yucca moth activity, some of which I have photographed and posted here: http://www.flickr.com/photos/mikesmode/sets/72157625733198704/detail/.

The National Center for Biotechnology Information has two abstracts on its web site that touch on the subjects of hybridization of yucca in nature and their pollinators:

The full publications are available for download from the NCBI web pages listed above.

Migrate SharePoint site collection to host header site collection

I recently had to devise a way to migrate a non-host header site collection in one farm to another host header (named) site collection in a different farm. So I devised a PowerShell script to accomplish this.

The script was used from going from an older SP 2010 patch level farm at Company A to a newer patch level farm at Company B, and from a non-host header site collection to a host header site collection. Here is the script:

# Script - M Hector - migrate site collection farm A - B, 
# managed path site collection to host header site collection.

# Purpose was to restore a non-host header site collection from an old content DB to a host  
# header site collection in the current content DB.  Content DBs were from different farms.

# The web application may or may not also have its own host header name.  (In this case, it
# does.)

# NOTES:
# Loaded this script in PowerShell ISE on a WFE or CA server and put breakpoints on the
# lines of code below that say, "INSERT BREAKPOINT HERE!"

Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

$oldContentDBBackupFile = "c:\temp\wss-content-old.bak"
$oldContentDB = "WSS_Content_Old"
$currentContentDB = "WSS_Content_Current"

$sqlDMBS = "sqldbms-1"
$webApp = "http://www.yuccas.xyz"

$oldSiteColl = "$webApp/" <# (non-host header site collection named '/' in root of current web app) #>
$newSiteColl = "http://rosettes.yuccas.xyz" <# (host header site collection) #>

# Dismounted the current content DB (not necessary when an old site collection's GUID does not
# exist in the current farm).
Dismount-SPContentDatabase -Identity $currentContentDB

# Mounted the old content DB to the desired SP web application (which in my case also has a host
# header called www.yuccas.xyz, but again, not required by any means).
Mount-SPContentDatabase $oldContentDB -DatabaseServer $sqlDMBS -WebApplication $webApp

# Got a reference to the content DB just attached.
$contentdb = Get-SPContentDatabase | Where-Object { $_.Name -match $oldContentDB }

# Upgraded the old content DB so that its version is consistent with the current farm patch level.
Upgrade-SPContentDatabase -Identity $contentdb

Write-Host "INSERT BREAKPOINT HERE!"

# Backed up the non-host header site collection from the old content DB using stsadm.exe (on purpose).
# In my case, the site collection is '/', the root site collection, but the site collection could
# be something entirely different, like '/sites/pubsite' (http://www.yuccas.xyz/sites/pubsite).
stsadm.exe -o backup -url $oldSiteColl -filename $oldContentDBBackupFile

Write-Host "INSERT BREAKPOINT HERE!"

# Dismounted the old content DB, since I now have what we want.
Dismount-SPContentDatabase -Identity $oldContentDB

Write-Host "INSERT BREAKPOINT HERE!"

# Remounted the current content DB.  It was important to tweak the MaxSiteCount to something appropriate
# for whatever count of site collections plus one or two more so that the stsadm.exe command will
# succeed below!  (Otherwise stsadm.exe will err out.)
Mount-SPContentDatabase $currentContentDB -DatabaseServer $sqlDMBS -WebApplication $webApp -MaxSiteCount 5 -WarningSiteCount 0

Write-Host "INSERT BREAKPOINT HERE!"

# Used stsadm.exe to restore the backed-up non-host header site collection to the host header site
# collection whose name is $newSiteColl (http://rosettes.yuccas.xyz).
stsadm.exe -o restore -url $newSiteColl -filename $oldContentDBBackupFile -hostheaderwebapplicationurl $webApp -overwrite

# Must not forget that with host header site collections, we have to manually add IIS bindings to the
# web application on each WFE / CA / app server of the farm.  Plus we either either need a DNS entry
# or a HOSTS file entry for the named site collection before it will be accessible in the browser.
Write-Host "Done.  " -nonewline
Write-Host "Add IIS bindings to all WFEs, update internal DNS with new record, then navigate to $newSiteColl and test."

Problem: Internet Explorer – “setup_exe could not be downloaded” – (with/without ClickOnce installs)

When I was attempting to run an installation from a secure (https) web site, despite having Enhanced Security Configuration disabled, despite having the site in my Trusted Sites zone, I still received the aforementioned error.  I did the following to fix it:

Clicked the gear toolbar icon in the upper right corner of IE or click the Options menu.
Clicked the Internet Options menu item.
Clicked the Advanced tab.
Scrolled down and cleared/deselected the “Do not save encrypted pages to disk” checkbox.
Refreshed the page and tried again. I then saw “Do you want to run or save setup.exe (nnnn KB) from website.xyz?” The Run and Save buttons were finally available and the filename was setup.exe instead of setup_exe.

This error can occur anytime you try to download an executable. I came across it trying to do a ClickOnce installation from an HTTPS site.

Also, sometimes the ClickOnce installation will begin but it will fail when it itself tries to download additional components and resources (aka the “An error occurred while downloading a required file” error).