Jekyll2018-04-26T09:07:17+00:00http://artokai.net/@artokaiThe blog of Arto KaitosaariArto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netYammer, Groups, Teams… Which one should I use?2017-02-19T00:00:00+00:002017-02-19T00:00:00+00:00http://artokai.net/2017/AboutYammerGroupsAndTeams<p>For a while there has been a lot of confusion about different collaboration
options in O365. It offers multiple different tools (SharePoint Team Sites,
Yammer, O365 Groups and Microsoft Teams), which seem to have a lot of
overlapping functionalities.</p>
<p>But Microsoft’s plans for these different tools are finally taking shape and
the role of each tool is becoming more clear. To help out our customers,
I wrote a small blog post about the subject in Affecto’s Blog.</p>
<p>So if you too feel a bit confused about the different collaboration options,
head on to <a href="http://www.affecto.com/insights/blog/future-collaboration-office-365/">Affecto’s blog</a>
and check which tool you should use and when.</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netFor a while there has been a lot of confusion about different collaboration options in O365. It offers multiple different tools (SharePoint Team Sites, Yammer, O365 Groups and Microsoft Teams), which seem to have a lot of overlapping functionalities.SharePoint Framework and GitLab Continuous Integration2016-12-13T00:00:00+00:002016-12-13T00:00:00+00:00http://artokai.net/2016/SPFXContinuousIntegration<p>Microsoft provides quite nice build tools for the upcoming
<a href="https://github.com/SharePoint/sp-dev-docs">SharePoint Framework</a>.
In addition to building the project using <code class="highlighter-rouge">gulp build</code>, you can also
run your unit tests using <code class="highlighter-rouge">gulp test</code>. Having unit tests is nice,
but having them executed automatically whenever you commit code to your source
control system is even nicer. In this blog post I’ll demonstrate
how you can setup automatic builds and unit testing using
<a href="https://about.gitlab.com/">GitLab</a> and <a href="https://www.docker.com/">Docker</a>.</p>
<p>In GitLab builds are executed by something called “build runners”. Build
runners are servers/processes which execute build scripts whenever new commits are
made to the source code repositories. To configure your repository to run these
automatic builds, just add a configuration file called `.gitlab-ci.yml’ in
the root of your project. This tells GitLab to pick up your commits and execute
build tasks defined in that configuration file.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-12-13_buildagent.jpg" style="max-width: 710px" alt="GitLab Build Runner" />
</figure>
<p>The nice thing about these build runners is that they can be configured to
support Docker images. This means that we do not need to install node, gulp,
or any other build tools required by the SharePoint Framework on the build
servers themselves. We can just tell GitLab to execute the build commands
on a Docker image that contains the necessary build tools. This way we can use the same
build servers to build multiple different kind of projects without worrying
that their dependencies would somehow conflict which each other.</p>
<p>The Docker image we use in this setup can be found in the <a href="https://hub.docker.com/">Docker Hub</a>
as <a href="https://hub.docker.com/r/artokai/spfx-ci/">artokai/spfx-ci</a>. It has all the
necessary SPFX build tools installed and it also contains a preloaded set of
npm packages required by the SPFX framework. I’ll explain the preloading part a little bit later…</p>
<p>The simplest version of actual configuration file (<code class="highlighter-rouge">.gitlab-ci.yml</code>) which triggers the automatic
builds looks like this:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">artokai/spfx-ci</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">test</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">npm install --no-optional</span>
<span class="na">execute-build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp build</span>
<span class="na">execute-test</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp test</span>
</code></pre></div></div>
<p>Basically we just tell GitLab to use our Docker image when building the project
and then we define two different stages (<code class="highlighter-rouge">gulp build</code> and <code class="highlighter-rouge">gulp test</code>) for the
actual build process. The <code class="highlighter-rouge">before_script</code> section is used to download all required
npm packages before <code class="highlighter-rouge">build</code> and <code class="highlighter-rouge">test</code> stages are executed. This is required since you probably
don’t have the <code class="highlighter-rouge">node_modules</code> in your source control system and you need to download those on
build agent (or actually on the Docker image running on the build agent).</p>
<p>The configuration listed above works but it does have some problems. Whenever a new commit is made,
the build process is executed on a “clean” build agent. This means that all of the files contained
in the <code class="highlighter-rouge">node_modules</code> (30 000 files, ~200MB) are downloaded and installed <strong>on every commit</strong>.
And to make things worse, a clean environment is created for all of the
stages defined in your <code class="highlighter-rouge">.gitlab-ci.yml</code> file. This means that the full SPFX framework is downloaded
and installed <strong>twice</strong> whenever you make a change in your source code repository.</p>
<p>Our Docker image already contains a set of common dependencies required by the SharePoint Framework.
So we can speed up the build process by “prepopulating” our project’s <code class="highlighter-rouge">node_modules</code>-directory before
running the <code class="highlighter-rouge">npm install</code> command. But instead of copying all those files, we’re just going to
create a symlink pointing to our pre-prepared folder. (The <code class="highlighter-rouge">npm prune</code> command is there to make sure
that the <code class="highlighter-rouge">node_modules</code> directory does not end up having any “extraneous” packages.)</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">artokai/spfx-ci</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">test</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ln -s /var/spfx_precache/node_modules/ node_modules</span>
<span class="pi">-</span> <span class="s">npm prune</span>
<span class="pi">-</span> <span class="s">npm install --no-optional</span>
<span class="na">execute-build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp build</span>
<span class="na">execute-test</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp test</span>
</code></pre></div></div>
<p>This change prevents our build agents from downloading all of those regular SPFX dependencies every time
it builds our projects. But what if our project has any additional project specific dependencies? Those
will still be downloaded on every build. Luckily GitLab supports also
<a href="https://docs.gitlab.com/ce/ci/yaml/#cache">caching</a>, which allows us to pass a set of files and
folders from one build to the next.</p>
<p>I first tried caching the entire <code class="highlighter-rouge">node_modules</code> directory, but caching 30 000 files took way too
much time. Therefore I ended up caching just the local npm cache which gets created when we install
our project specific packages for the first time. When the project specific modules are
required on the next build, npm finds them in the local cache folder and installs them from there.</p>
<p>Since GitLab supports caching only under the project directory, we need to tell npm to
use a project specific cache folder (<code class="highlighter-rouge">.npm_cache</code>) during the build. The size of this folder will
be quite small since all the regular SPFX modules are already present
in our symlinked <code class="highlighter-rouge">node_modules</code> folder and won’t therefore be downloaded and cached by npm.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">artokai/spfx-ci</span>
<span class="na">cache</span><span class="pi">:</span>
<span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">$CI_BUILD_REF_NAME"</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">.node_cache/</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">build</span>
<span class="pi">-</span> <span class="s">test</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">ln -s /var/spfx_precache/node_modules/ node_modules</span>
<span class="pi">-</span> <span class="s">npm prune</span>
<span class="pi">-</span> <span class="s">mkdir -p .node_cache</span>
<span class="pi">-</span> <span class="s">npm install --no-optional --cache .node_cache --cache-min Infinity</span>
<span class="na">execute-build</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp build</span>
<span class="na">execute-test</span><span class="pi">:</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">gulp test</span>
</code></pre></div></div>
<p>The next step would naturally be uploading the resulting JavaScript files to a CDN as
part of the automatic build process. This would allow them to be automatically updated
in our staging environment whenever all of the unit tests have passed. But this will
be a topic for another blog post…</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netMicrosoft provides quite nice build tools for the upcoming SharePoint Framework. In addition to building the project using gulp build, you can also run your unit tests using gulp test. Having unit tests is nice, but having them executed automatically whenever you commit code to your source control system is even nicer. In this blog post I’ll demonstrate how you can setup automatic builds and unit testing using GitLab and Docker.Using TypeScript 2.1 in SPFX Drop 62016-12-10T00:00:00+00:002016-12-10T00:00:00+00:00http://artokai.net/2016/UsingTS21InSPFXdrop6<p><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html">TypeScript 2.1</a> was released
just a couple of days ago. This update finally allows us to use async/await while
targeting the ES5 flavour of Javascript. With “await” we no longer need to use callbacks
or chain our promises together with those ugly then-chains when working with asynchronous code. With async/await
we can treat our asynchronous methods almost as if they were just regular function calls.</p>
<p>Unfortunately SPFX Drop 6 is still using TypeScript 2.0 but we can fix this with the help of
<a href="https://docs.npmjs.com/cli/shrinkwrap">npm shrinkwrap</a>. By adding a file called <code class="highlighter-rouge">npm-shrinkwrap.json</code>
to the root of our project we can override the typescript version used by SPFX. The contents of the file
should be as follows:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="s2">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"@microsoft/sp-build-web"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^0.8.1"</span><span class="p">,</span><span class="w">
</span><span class="s2">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"typescript"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="s2">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^2.1.4"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>When you now run the command <code class="highlighter-rouge">npm install</code> it will force the SPFX build tools to use the new
TypeScript version. After npm has finished updating, we can start waiting for promises
in our webparts like this.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">public</span> <span class="k">async</span> <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">fact</span> <span class="o">=</span> <span class="kr">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">getFact</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nx">domElement</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`
<div class="</span><span class="p">${</span><span class="nx">styles</span><span class="p">.</span><span class="nx">fact</span><span class="p">}</span><span class="s2">">
</span><span class="p">${</span><span class="nx">fact</span><span class="p">}</span><span class="s2">
<cite>The Internet Chuck Norris Database</cite>
</div>`</span><span class="p">;</span>
<span class="p">}</span>
<span class="kr">protected</span> <span class="nx">getFact</span><span class="p">()</span> <span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">string</span><span class="o">></span>
<span class="p">{</span>
<span class="kd">const</span> <span class="na">url</span><span class="p">:</span> <span class="nx">string</span> <span class="o">=</span> <span class="s1">'https://api.icndb.com/jokes/random'</span><span class="p">;</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">string</span><span class="o">></span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">"GET"</span><span class="p">,</span> <span class="nx">url</span><span class="p">,</span> <span class="kc">true</span> <span class="p">);</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">response</span><span class="p">:</span> <span class="nx">any</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">xhr</span><span class="p">.</span><span class="nx">responseText</span><span class="p">);</span>
<span class="nx">resolve</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">joke</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="nx">reject</span><span class="p">;</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that in order for this to work, we need to add the <code class="highlighter-rouge">async</code> keyword to our webpart’s
render() method and remove the return value type which used to be <code class="highlighter-rouge">void</code>. The await keyword
can be used to wait for any method that return a promise or is marked with the <code class="highlighter-rouge">async</code> keyword.</p>
<p>Example code for this blog article can be found in <a href="https://github.com/artokai/samples/tree/master/SPFX/TS21_in_SPFX_drop6">my GitHub repository</a>.</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netTypeScript 2.1 was released just a couple of days ago. This update finally allows us to use async/await while targeting the ES5 flavour of Javascript. With “await” we no longer need to use callbacks or chain our promises together with those ugly then-chains when working with asynchronous code. With async/await we can treat our asynchronous methods almost as if they were just regular function calls.Managing SharePoint external users in a separate Azure AD instance2016-11-06T00:00:00+00:002016-11-06T00:00:00+00:00http://artokai.net/2016/ManageExternalSPUsersInSeparateAAD<p>In <a href="/2016/SPOnlineAndAzureB2B/">my last blog post</a> I talked about how you can
utilize <a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-collaboration-overview/">Azure AD B2B</a>
to invite external users to your SharePoint sites. In this blog post I’ll describe an
alternative method which you can use, if you want to manage the external user accounts
in your own Azure Active Directory.</p>
<p>The idea is to create a separate Azure AD for your external users and use that to manage
and maintain the identities of your external users. Toni Pohl
has already <a href="http://blog.atwork.at/post/2014/12/06/How-to-use-external-users-in-SharePoint-Online-with-a-cost-free-Azure-Active-Directory">blogged about this approach</a>
back in 2014. His solution was to create a user account in a separate Azure AD,
send a SharePoint sharing invitation to the user’s real email address and hope that
the user will sign in using the user account created in the separate Azure AD.</p>
<p>The method I’m going to describe next does not require you to send any
sharing invites from SharePoint and thus makes it impossible for the end user
to accept the invite with some other user account than the one prepared for him.
But this currently comes with a cost. It will leave the email address field in
the external user’s user profile empty and therefore SharePoint will not be
able to send any email notifications to the user.</p>
<p>Consider yourself warned!</p>
<h3 id="step-1-create-a-separate-azure-ad-to-host-your-external-users">Step 1: Create a separate Azure AD to host your external users</h3>
<p>First we need to create a new Azure AD instance which will contain our external users.
So login to <a href="https://manage.windowsazure.com">https://manage.windowsazure.com</a>, select “Active Directory” and press “New”
to create a new Azure AD instance. Creating a new Azure AD instance is pretty straight forward.
You basically just need to provide a name and a location for the new directory and you’re all set.</p>
<h3 id="step-2-associate-a-domain-name-with-your-new-ad-optional">Step 2: Associate a domain name with your new AD (Optional)</h3>
<p>By default all user’s in your new directory will have usernames like
“john.smith@contosocustomers.onmicrosoft.com”. In business scenarios you probably want to use
some other domain than “onmicrosoft.com”. If you want to change that to something like
“contosocustomers.com” you probably should do that now, before creating any actual users.</p>
<p>To add a new domain to your directory, you need to select the “Domains” tab of
your newly created domain and press the “Add a custom domain”-link. This will open up
a wizard that will guide you through the rest of the process. Just remember
that adding a new domain requires you to prove that you actually manage the domain you’re adding,
so some DNS changes are required.</p>
<h3 id="step-3-add-your-users-external-users-in-the-new-ad">Step 3: Add your users external users in the new AD</h3>
<p>To add a new user in your new Active Directory, navigate to your newly created
Active Directory, select Users and press the “Add User”-button.</p>
<p>Select “New user in your organization” as the user’s type and follow the
instructions provided by the wizard to create a user account for your external user.
When you’re presented with a temporary password, write it down since you’ll need to send it to
your external user later on.</p>
<h3 id="step-4-add-your-external-users-as-guests-in-your-primary-ad">Step 4: Add your external users as guests in your primary AD</h3>
<p>SharePoint allows you to grant permissions to only users in the primary Azure AD
associated with your subscription. So even though you have now created a user account for your
external user, you’ll now need to add that account as a “guest” in your primary directory.
This is actually something that the SharePoint’s invitation process will do behind the
scenes automatically when a user accepts an invite to a SharePoint site.</p>
<p class="notice--info"><strong>Note:</strong> The next step needs to be done through the classic portal, since currently the
new portal does not support adding users from another directories.</p>
<p>To create the required “guest-proxy”, navigate to your primary directory and press “Add user”
button in the Users-tab. You need to select “User in another Microsoft Azure AD directory”
as the user type and fill in the user name -field. As user’s role we would like to fill
in “Guest”, but this is unfortunately not possible through the UI. So let’s leave it to
“User” for now. We’ll soon fix it with PowerShell.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-11-06_AddUserToPrimaryDirectory.jpg" style="max-width: 770px" alt="Add user from another Azure AD" />
</figure>
<p>Now our external user is added to the primary domain and can be found by SharePoint Online.
However the user’s role is still “User” and SharePoint does not like this. If the
user would now try to login to SharePoint Online using his new user account, he would receive an error message saying
“We’re sorry, but john.smith@contosocustomers.com can’t be found in the contoso.sharepoint.com directory.”.</p>
<p>To fix this we will need to change the user’s role to “Guest” using the following
PowerShell script:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$cred</span> <span class="o">=</span> <span class="nb">get-credential
</span>Connect-MsolService -Credential <span class="nv">$cred</span>
<span class="nv">$user</span> <span class="o">=</span> Get-MsolUser | <span class="nb">Where</span>-Object <span class="o">{</span> <span class="nv">$_</span>.SignInName -eq <span class="s2">"john.smith@contosocustomers.com"</span><span class="o">}</span>
<span class="nb">Set</span>-MsolUser -ObjectId <span class="nv">$user</span>.ObjectId -UserType Guest
</code></pre></div></div>
<p class="notice--info"><strong>Note:</strong> At this point we would also like to set user’s ProxyAddresses field to include the
user’s real email address. This is the field that SharePoint will use for the user’s email address.<br />
However Azure AD does not currently support this. The only way to set the ProxyAddresses-field is through
Exchange Online and it cannot be done to users without actual mailboxes.</p>
<h3 id="step-5-grant-permissions-in-sharepoint">Step 5: Grant permissions in SharePoint</h3>
<p>After the guest user account is created in the directory associated with your O365 subscription,
you finally go to your SharePoint site and grant the necessary permissions to your external users.
When granting the permissions, you should use the newly created user account (for example
“john.smith@contosocustomers.com”) instead of the user’s real email address. Since we already
created a “guest-proxy” for that user account, SharePoint will find this user and no sharing
invitations will be emailed to anyone.</p>
<p class="notice--info"><strong>Note:</strong> Since you are granting permissions to an external user, you need to allow external
sharing for the site collection before you try to grant permissions to the user.</p>
<h3 id="step-6-send-the-login-information-to-your-external-user">Step 6: Send the login information to your external user</h3>
<p>The final step is to contact your external user and provide him with the username, password
and the url of your shared site. If you followed these instructions correctly, he/she should be
able to login to your SharePoint site without any problems.</p>
<h2 id="conclusion-and-final-thoughts">Conclusion and final thoughts</h2>
<p>It is indeed possible to manage external user accounts in a separate Azure AD and grant permissions
to those user accounts without emailing any sharing invitations to your users. But not having
the user’s real email address populated in SharePoint is a major drawback of this method.</p>
<p>The process also includes quite a lot of steps for creating a single user. I would like
to write a single PowerShell script that would automate the whole process, but
it seems that importing users from another Azure AD cannot be done programmatically.</p>
<p>So I’m not fully satisfied with this process and I suspect that the whole invite process is
so strongly built-in to Azure AD, that resistance is futile. Perhaps I’ll just take a closer
look on what’s cooking inside the MS Graph API and check if the new
<a href="https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/invitation">Invitation Manager-endpoint</a>
(currently in beta) could somehow be used to make the SharePoint external user management easier…</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netIn my last blog post I talked about how you can utilize Azure AD B2B to invite external users to your SharePoint sites. In this blog post I’ll describe an alternative method which you can use, if you want to manage the external user accounts in your own Azure Active Directory.Using Azure B2B to invite external users to SharePoint Online2016-10-13T00:00:00+00:002016-10-13T00:00:00+00:00http://artokai.net/2016/SPOnlineAndAzureB2B<p class="notice--info"><strong>Note:</strong> This post is related to Azure AD B2B, which is still in preview. This means that things may still change
before the final version Azure AD B2B is released.</p>
<p><a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-collaboration-overview/">Azure AD B2B</a>
allows you to invite users from partner companies and let them access your
resources using partner-managed identities. Using a partner-managed identity means
that you don’t have to create or maintain the user accounts for your external users.
The invited users can login to your SharePoint site using his/her own corporate user account,
which is a lot better than having them using their personal Microsoft accounts.</p>
<p>If your partner does not have an Azure AD tenant, then B2B will
notice this and automatically create a new Azure AD tenant for them based on the domain
name of the invited email address. These tenants are called “viral tenants”.</p>
<p>At this point the tenant and the automatically created
user accounts (“viral users”) within are not owned or maintained by any specific organization. Users
can reset their own passwords and so on, but no-one is assigned as an administrator
for the viral tenant. Of course your partner can later on claim the ownership of
the viral tenant by registering the domain name in question in their own subscription.
After that it will become just a regular Azure AD tenant.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-10-13_b2bflow.jpg" style="max-width: 500px" alt="B2B invitation process" />
</figure>
<p>When using Azure AD B2B to invite users in partner companies, the invitation process will
also create a corresponding Guest user in your own Azure AD tenant. The created guest
account is similar to the one that gets created automatically when you share a SharePoint site
to an external user. The guest account created by B2B is actually created before the user
has even accepted the invitation, which means that you don’t have to wait for the user to
accept the invitation before granting him/her permissios to your SharePoint site.</p>
<h2 id="how-to-invite-users-with-azure-ad-b2b">How to invite users with Azure AD B2B?</h2>
<p>Currently the Azure AD B2B invitation process starts with a CSV-file, which contains a
list of email addresses you want to invite as guests. This CSV file is then
uploaded to Azure AD admin portal, which starts the invitation process.</p>
<p>So the first step of inviting people is to create a CSV file for example using
MS Excel:</p>
<pre><code class="language-csv">Email,DisplayName,InvitationText,InviteRedirectUrl,InvitedToApplications,InvitedToGroups,CcEmailAddress,Language
john@contoso.com,John Smith,,https://contoso.sharepoint.com/sites/b2bsite,,,,en
walter@contoso.com,Walter Harp,,https://contoso.sharepoint.com/sites/b2bsite,,,,en
ben@contoso.com,Ben Smith,,https://contoso.sharepoint.com/sites/b2bsite,,,,en
</code></pre>
<p>The format of the CSV-file is described <a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-references-csv-file-format/">here</a>
and only the fields <code class="highlighter-rouge">Email</code> and <code class="highlighter-rouge">DisplayName</code> are actually required for the invitation process.
Note that you should always leave the <code class="highlighter-rouge">CcEmailAddress</code> field empty.
If this field is used, the invitation cannot be used for viral user or tenant creation.</p>
<p class="notice--info"><strong>Note:</strong>
If you live outside the US, you should be aware that when the B2B documentation talks about CSV it actually means
<strong>comma-separated-values</strong>. This means that editing this file in Excel may be a bit difficult,
if your CSV-files are usually separated by some other character. For example here in Finland we (and our
MS Excels) usually use semicolon (;) instead of commas (,) in our CSV files.</p>
<p>To have your CSV-file processed, you need to login to Azure AD admin portal,
click “Add User” and select the type of the user to be <code class="highlighter-rouge">Users in parner companies</code>.
A detailed walkthrouh (with screenshots) of this can be found <a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-detailed-walkthrough/">here</a>.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-10-13_b2b_upload_csv.jpg" style="max-width: 781px" alt="B2B invitation process" />
</figure>
<p class="notice--info"><strong>Note:</strong> Currently Azure AD B2B functionalities are available only in the Azure AD classic portal.
So in order to have your CSV file processed, you need to login to <a href="https://manage.windowsazure.com">https://manage.windowsazure.com</a>
and access your Active Directory from there.</p>
<h2 id="granting-permissions-to-sharepoint">Granting permissions to SharePoint</h2>
<p>After the CSV file has been processed, the invited users will immediately appear
as guests in your own Azure AD and you can start granting permissions to them in
SharePoint. Adding a lot of users for example to site’s Visitors-group can however require
quite a lot of manual work. You can automate this process by either granting
permissions to the site by using an Azure AD group or by using PowerShell.</p>
<h3 id="option-1-granting-permissions-through-azure-ad-groups">Option 1: Granting permissions through Azure AD groups</h3>
<p>If you create an Azure AD group for your invited users, you can define its
group id in the <code class="highlighter-rouge">InvitedToGroups</code> field of the CSV. This will instruct B2B
to automatically add your invited users to that group. If you can grant permissions
to this Azure AD group in SharePoint, then all of your invited users will also
gain access to your SharePoint site.</p>
<p>Microsoft has published <a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-detailed-walkthrough/#adding-carol-to-the-contoso-directory-granting-access-to-apps-and-giving-group-membership">instructions</a>
on how to automatically add invited users to specified AD groups. If you take
this route, be aware that you can also find the group’s Object ID in the Azure Portal and
you don’t necessary need to user PowerShell to look it up (as stated in the Microsoft’s
instructions).</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-10-13_group_objectid.jpg" style="max-width: 720px" alt="Group's Object Id" />
</figure>
<h3 id="option-2-granting-permissions-directly-to-sharepoint-using-powershell">Option 2: Granting permissions directly to SharePoint using PowerShell</h3>
<p>If you don’t want to start managing SharePoint permissions through AD groups, you can
add your invited users directly to SharePoint groups. Luckily we can automate this step
with a little PowerShell script.</p>
<p>Since we already have a list of users defined in a CSV file, we can use the same CSV file
to grant permissions to our SharePoint site. We just need to add one
additional field (<code class="highlighter-rouge">SPGroups</code>) to the CSV file in order for our script to know to which
SharePoint groups the user should be added. I’ve chosen to separate multiple group names
with a plus sign (+).</p>
<pre><code class="language-csv">Email,DisplayName,InvitationText,InviteRedirectUrl,InvitedToApplications,InvitedToGroups,CcEmailAddress,Language,SPGroups
john@contoso.com,Jeff Smith,,https://contoso.sharepoint.com/sites/b2bsite,,,,en,"B2Bsite Visitors
walter@contoso.com,Walter Harp,,https://contoso.sharepoint.com/sites/b2bsite,,,,en,"B2Bsite Visitors"
ben@contoso.com,Ben Smith,,https://contoso.sharepoint.com/sites/b2bsite,,,,en,"B2Bsite Members+B2Bsite Reviewers"
</code></pre>
<p>The <a href="https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2b-references-csv-file-format/">CSV format reference</a>
for B2B does not say anything about additional fields in the CSV-file,
but they do not seem to break the invitation process in any way. The B2B process just
ignores them which is great from our point of view since we can control both B2B and our PowerShell script
with exactly the same file.</p>
<p>After the extended CSV file has been processed by Azure AD B2B you can add the invited
users to SharePoint groups by executing <a href="https://gist.github.com/artokai/6c79ec4169eb35ba111c80aac0b5ec81">a little script</a>
in PowerShell. In order for the script to work, you need to have the <a href="https://github.com/OfficeDev/PnP-PowerShell">PnP PowerShell Cmdlets</a> installed on your system.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.\Add-B2BUsersToSite.ps1 -Verbose <span class="se">`</span>
-CSVFilename c:\b2b_users.csv <span class="se">`</span>
-SiteUrl https://contoso.sharepoint.com/sites/b2bsite
VERBOSE: Credentials not supplied, prompting <span class="k">for </span>them.
Adding john@contoso.com to site
VERBOSE: Adding user to groups
VERBOSE: - Adding user to <span class="nb">group</span>: B2Bsite Visitors
Adding walter@contoso.com to site
VERBOSE: Adding user to groups
VERBOSE: - Adding user to <span class="nb">group</span>: B2Bsite Visitors
Adding ben@contoso.com to site
VERBOSE: Adding user to groups
VERBOSE: - Adding user to <span class="nb">group</span>: B2Bsite Members
VERBOSE: - Adding user to <span class="nb">group</span>: B2Bsite Reviewers
</code></pre></div></div>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netNote: This post is related to Azure AD B2B, which is still in preview. This means that things may still change before the final version Azure AD B2B is released.Did the UserName field just disappear from the User Information list in SharePoint Online?2016-08-19T00:00:00+00:002016-08-19T00:00:00+00:00http://artokai.net/2016/UserNameDisappearedFromUserInfoList<p>One of our SharePoint AddIns just stopped working in SharePoint Online even though
we had done no changes in the actual codebase. It turned out that a field called <code class="highlighter-rouge">UserName</code> was
not present in the
<a href="https://blogs.technet.microsoft.com/marj/2016/03/14/what-is-hidden-user-information-userinfo-list-in-sharepoint-20102013-and-how-to-fix-when-it-causes-a-site-collection-to-show-old-user-metadata-properties-in-people-picker-control-or-in-a-person-or/">User Information List</a>
of a newly created site collection.</p>
<p>I checked a couple of newly created site collections and compared them with some older ones and
this out-of-the-box field is indeed no longer present in the new site collections. And to make things worse,
this field is missing only in our production tenant. When I create new site collections in our development tenant
the field is still present and our code works just fine.</p>
<p>I can think of two possible explanations for this missing field and I really don’t like either one of them:</p>
<ol>
<li>There is a temporary problem with our production tenant (unlikely, since everything else seems to work just fine)</li>
<li>Microsoft just decided to remove this field from the list without telling us about it.</li>
</ol>
<p>Just a few weeks ago Microsoft broke their customers’ solutions by
<a href="http://www.sharepointnutsandbolts.com/2016/08/sandbox-code-disabled-in-Office-365.html">disabling sandboxed solutions</a>
without bothering to inform them properly about it. And now they seem to have made another change which breaks things up.
This time the change is a smaller one, but it still took us by surprise. I really hope that Microsoft will figure out some way
to keep us informed about these things.</p>
<p>So if you’re facing issues with the User Information List, you can check if the UserName field is present in your tenant with the following PowerShell Script.
In order to run it, you need to have the magnificent <a href="https://github.com/OfficeDev/PnP-PowerShell">PnP PowerShell Cmdlets</a> installed on your system.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$creds</span> <span class="o">=</span> <span class="nb">Get-Credential</span>
<span class="nv">$siteUrl</span> <span class="o">=</span> <span class="s2">"https://tenantname.sharepoint.com/sites/your_site_name"</span>
Connect-SPOnline -Url <span class="nv">$siteUrl</span> -Credentials <span class="nv">$creds</span>
<span class="nv">$web</span> <span class="o">=</span> Get-SPOWeb
<span class="nv">$ctx</span> <span class="o">=</span> Get-SPOContext
<span class="nv">$siteUserInfoList</span> <span class="o">=</span> <span class="nv">$web</span>.SiteUserInfoList;
<span class="nv">$ctx</span>.Load<span class="o">(</span><span class="nv">$siteUserInfoList</span><span class="o">)</span>
<span class="nv">$ctx</span>.Load<span class="o">(</span><span class="nv">$siteUserInfoList</span>.Fields<span class="o">)</span>
<span class="nv">$ctx</span>.ExecuteQuery<span class="o">()</span>
<span class="nv">$siteUserInfoList</span>.Fields | <span class="nb">Where</span>-Object <span class="o">{</span> <span class="nv">$_</span>.InternalName -eq <span class="s2">"UserName"</span><span class="o">}</span>
Disconnect-SPOnline
</code></pre></div></div>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netOne of our SharePoint AddIns just stopped working in SharePoint Online even though we had done no changes in the actual codebase. It turned out that a field called UserName was not present in the User Information List of a newly created site collection.The importance of Try-Catch statements in SharePoint ScriptParts2016-07-06T00:00:00+00:002016-07-06T00:00:00+00:00http://artokai.net/2016/ImportanceOfTryCatchInScriptParts<p>I received a report that one SharePoint <a href="https://blogs.msdn.microsoft.com/vesku/2014/07/08/introducing-app-script-part-pattern-for-office365-app-model/">ScriptPart</a>
was not functioning properly and spent quite a lot of time finding
out the reason for it. It was a bit hard to find out the cause of the issue partly because
the issue concerned only IE and happened only when IE developer tools were not opened.
This meant that I could not easily step through the code using IE’s built-in debugger.</p>
<p>But the most time consuming thing was the error report itself. It turned out that the
ScriptPart mentioned in the issue report was actually working just fine, but was just
never executed. The SharePoint page in question had multiple ScriptParts and all the
other ScriptParts <em>appeared</em> to be working just fine. But in the background one of those
other ScriptParts was throwing an unhandled exception…</p>
<p>To demonstrate this behaviour, I created two simple ScriptParts and embedded them to a
SharePoint page using two separate Content Editor webparts. The first ScriptPart will throw an exception
since it tries to call a method that does not exist.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">div</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"scriptpart_one"</span><span class="o">></span><span class="nx">Loading</span><span class="p">...</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"text/javascript"</span><span class="o">></span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">SP</span><span class="p">.</span><span class="nx">SOD</span><span class="p">.</span><span class="nx">executeFunc</span><span class="p">(</span><span class="s1">'sp.js'</span><span class="p">,</span> <span class="s1">'SP.ClientContext'</span><span class="p">,</span> <span class="nx">spReady</span><span class="p">);</span>
<span class="kd">function</span> <span class="nx">spReady</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">nonExistingFunction</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">elem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"scriptpart_one"</span><span class="p">);</span>
<span class="nx">elem</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="s2">"Hello from ScriptPart One!"</span>
<span class="p">}</span>
<span class="p">})();</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre></div></div>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">div</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"scriptpart_two"</span><span class="o">></span><span class="nx">Loading</span><span class="p">...</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"text/javascript"</span><span class="o">></span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">SP</span><span class="p">.</span><span class="nx">SOD</span><span class="p">.</span><span class="nx">executeFunc</span><span class="p">(</span><span class="s1">'sp.js'</span><span class="p">,</span> <span class="s1">'SP.ClientContext'</span><span class="p">,</span> <span class="nx">spReady</span><span class="p">);</span>
<span class="kd">function</span> <span class="nx">spReady</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">elem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"scriptpart_two"</span><span class="p">);</span>
<span class="nx">elem</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="s2">"Hello from ScriptPart Two!"</span>
<span class="p">}</span>
<span class="p">})();</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre></div></div>
<p>As you can see from the following screenshot, SharePoint will never
execute the <code class="highlighter-rouge">spReady</code> callback of the second ScriptPart, because
the callback in the first ScriptPart does not handle the error properly.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-07-06_trycatch.png" style="max-width: 720px" alt="Client WebPart Development Cycle" />
</figure>
<p>If the problematic ScriptPart would have a try-catch statement inside
its executeFunc callback, it would not not prevent other registered
callbacks from executing. A better version of the first ScriptPart is
presented below.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">div</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"scriptpart_one"</span><span class="o">></span><span class="nx">Loading</span><span class="p">...</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"text/javascript"</span><span class="o">></span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">SP</span><span class="p">.</span><span class="nx">SOD</span><span class="p">.</span><span class="nx">executeFunc</span><span class="p">(</span><span class="s1">'sp.js'</span><span class="p">,</span> <span class="s1">'SP.ClientContext'</span><span class="p">,</span> <span class="nx">spReady</span><span class="p">);</span>
<span class="kd">function</span> <span class="nx">spReady</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">elem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"scriptpart_one"</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nx">nonExistingFunction</span><span class="p">();</span>
<span class="nx">elem</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="s2">"Hello from ScriptPart One!"</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">displayError</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">displayError</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">errorMsg</span> <span class="o">=</span> <span class="s2">"Error: "</span> <span class="o">+</span> <span class="nx">e</span><span class="p">.</span><span class="nx">message</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">console</span> <span class="o">&&</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">errorMsg</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">elem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"scriptpart_one"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">elem</span><span class="p">)</span>
<span class="nx">elem</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">errorMsg</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">})();</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre></div></div>
<p>(Note that you should not call <code class="highlighter-rouge">console.log</code> without first checking
that it really exists. Unchecked calls to <code class="highlighter-rouge">console.log</code> is one of those bugs
which are hard to reproduce when IE’s debugger is running.)</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netI received a report that one SharePoint ScriptPart was not functioning properly and spent quite a lot of time finding out the reason for it. It was a bit hard to find out the cause of the issue partly because the issue concerned only IE and happened only when IE developer tools were not opened. This meant that I could not easily step through the code using IE’s built-in debugger.Url redirection service with ASP.NET Core2016-07-04T00:00:00+00:002016-07-04T00:00:00+00:00http://artokai.net/2016/UrlRedirectionServiceWithAspNetCore<p>I needed a personal URL redirection service to make it easier to publish
more memorable url addresses. For example, if you want to see my LinkedIn profile
you can now use the url <a href="http://aka.artokai.net/linkedin">http://aka.artokai.net/linkedin</a>
and you will be redirected to my linked in profile.</p>
<p>Actually I didn’t really <em>NEED</em> a url redirection service, but I wanted to have one.
And that’s almost the same thing right? So I did a quick google search for available
options and did not really like what I found. I wanted to host my redirection service
in Azure and I wanted to keep the costs as low as possible.
This meant that I did not want to pay for a database for example.</p>
<p>So when <a href="http://www.asp.net/core">ASP.NET core 1.0</a> was released I decided to build
my own url redirection service with only a minimal set of features and functionalities.
The service, which I named Azurl, holds all url aliases in memory and therefore does
not require a backing database server. To survive reboots the aliases are also
serialized to disk in JSON format.</p>
<p>Azurl is just a url redirection service. It does not contain any user interfaces at all.
All maintenance work (adding and removing aliases) are done through
a <a href="https://github.com/artokai/Azurl/blob/master/aliases.json">JSON file stored in a GitHub repository</a>.
When this file is changed, Azurl receives a notification about it through a webhook registered in GitHub.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-07-04_Azurl_Sequence.png" style="max-width: 720px" alt="Client WebPart Development Cycle" />
<figcaption style="text-align:center;">Azurl configuration process</figcaption>
</figure>
<p>In the end I’m quite happy with the results. Editing the alias-configuration can be done directly
from the GitHub UI and the new aliases are available almost immediately. Working with the new
ASP.NET Core was also a nice experience. I really liked the new built-in dependency injection
framework and configuration management model.</p>
<p>The service and sources can be found in <a href="http://aka.artokai.net/azurl">http://aka.artokai.net/azurl</a> =)</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netI needed a personal URL redirection service to make it easier to publish more memorable url addresses. For example, if you want to see my LinkedIn profile you can now use the url http://aka.artokai.net/linkedin and you will be redirected to my linked in profile.Email address is not the user’s identity in O3652016-06-07T00:00:00+00:002016-06-07T00:00:00+00:00http://artokai.net/2016/EmailIsNotUserIdentityInO365<p>Usually the user’s identity in O365 is the same thing as their email address, but
as a developer you should never assume that this is true for all users.
Sometimes the environment can be configured in a way that the User Principal Name (UPN) is
actually different than the user’s primary email address.</p>
<p>Assuming that the user’s UPN equals their email address can lead to subtle bugs which are
hard to spot during testing. In some organizations there might be just a couple of users
whose primary email address does not match their UPNs. It can be very tricky to find out
why your custom code works over 99% of the time, but seems to fail for those few users.</p>
<p>One situation where this problem can occur is demonstrated in the following sample, which
tries to create a new site collection and fails because John is actually
<code class="highlighter-rouge">john.smith@contoso.com</code> and not <code class="highlighter-rouge">john.smith@contoso.fi</code> which is just his primary email
address.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">tenant</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Tenant</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">props</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SiteCreationProperties</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Url</span> <span class="p">=</span> <span class="s">"https://contoso.sharepoint.com/sites/testsite"</span><span class="p">,</span>
<span class="n">Title</span> <span class="p">=</span> <span class="s">"TestSite"</span><span class="p">,</span>
<span class="n">Template</span> <span class="p">=</span> <span class="s">"STS#0"</span><span class="p">,</span>
<span class="n">StorageMaximumLevel</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
<span class="n">UserCodeMaximumLevel</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
<span class="n">Owner</span> <span class="p">=</span> <span class="s">"john.smith@contoso.fi"</span><span class="p">,</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">spoOperation</span> <span class="p">=</span> <span class="n">tenant</span><span class="p">.</span><span class="nf">CreateSite</span><span class="p">(</span><span class="n">props</span><span class="p">);</span>
<span class="n">ctx</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="n">spoOperation</span><span class="p">);</span>
<span class="n">ctx</span><span class="p">.</span><span class="nf">ExecuteQuery</span><span class="p">();</span>
</code></pre></div></div>
<p>It’s easy to slip in these kind of errors in your code since often the required user information
is stored in a SharePoint list. The <a href="https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.client.fielduservalue.aspx">FieldUserValue-class</a>
and especially its <a href="https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.client.fielduservalue.email.aspx">Email-property</a>
offer us easy access to the <em>wrong</em> information (which just happens to work 99% of the time and therefore the error goes unnoticed).
Unfortunately the FieldUserValue-class does not contain a direct property fors the user’s login name,
so we need to fetch it manually. One way to do this is to load it from the hidden
<a href="https://gallery.technet.microsoft.com/User-Information-List-in-8b420e8c">User Information List</a> stored in the root of the site collection.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">userFieldValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">FieldUserValue</span><span class="p">)</span> <span class="n">listItem</span><span class="p">[</span><span class="s">"CustomUserField"</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">userItem</span> <span class="p">=</span> <span class="n">web</span><span class="p">.</span><span class="n">SiteUserInfoList</span><span class="p">.</span><span class="nf">GetItemById</span><span class="p">(</span><span class="n">userFieldValue</span><span class="p">.</span><span class="n">LookupId</span><span class="p">);</span>
<span class="n">ctx</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="n">userItem</span><span class="p">);</span>
<span class="n">ctx</span><span class="p">.</span><span class="nf">ExecuteQuery</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">userName</span> <span class="p">=</span> <span class="n">userItem</span><span class="p">[</span><span class="s">"UserName"</span><span class="p">];</span>
</code></pre></div></div>
<p>If you are interested in finding out which of your users have a mismatch between their
UPNs and primary email addresses, you can list them using the following PowerShell script. The script
excludes all Guest-users since their UPN never matches their email-address anyway.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$cred</span> <span class="o">=</span> <span class="nb">Get-Credential
</span>Connect-MsolService -Credential <span class="nv">$cred</span>
Get-MsolUser |? <span class="o">{</span> <span class="nv">$_</span>.UserType -ne <span class="s1">'Guest'</span> <span class="o">}</span> | <span class="k">ForEach</span>-Object <span class="o">{</span>
<span class="nv">$user</span> <span class="o">=</span> <span class="nv">$_</span>;
<span class="nb">New-Object</span> -TypeName PSObject -Property @<span class="o">{</span>
UserPrincipalName <span class="o">=</span> <span class="nv">$user</span>.UserPrincipalName
PrimaryEmail <span class="o">=</span> <span class="o">(</span>
<span class="nv">$user</span> |
<span class="nb">select</span> -Expand ProxyAddresses |
? <span class="o">{</span><span class="nv">$_</span> -cmatch <span class="s1">'^SMTP\:.*'</span><span class="o">}</span>
<span class="o">)</span> -replace <span class="s1">'^SMTP:'</span>, <span class="s1">''</span>
<span class="o">}</span>
<span class="o">}</span> |
<span class="nb">Where</span>-Object <span class="o">{</span> <span class="nv">$_</span>.PrimaryEmail -ne <span class="s1">''</span> <span class="o">}</span> |
<span class="nb">Where</span>-Object <span class="o">{</span> <span class="nv">$_</span>.UserPrincipalName -ne <span class="nv">$_</span>.PrimaryEmail <span class="o">}</span>
</code></pre></div></div>
<p class="notice--info"><strong>Note:</strong> In order to run the script, you’ll need to have the Windows Azure Active Directory Module installed on your system.
You can find the necessary installation and usage instructions in <a href="https://technet.microsoft.com/en-us/library/dn975125.aspx">this technet article</a>.</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netUsually the user’s identity in O365 is the same thing as their email address, but as a developer you should never assume that this is true for all users. Sometimes the environment can be configured in a way that the User Principal Name (UPN) is actually different than the user’s primary email address.Tools for the new SharePoint framework2016-05-17T00:00:00+00:002016-05-17T00:00:00+00:00http://artokai.net/2016/ToolsForSharePointFramework<p>Vesa Juvonen and Waldek Mastykarz from the OfficeDev
Patterns & Practises -team have released a new Channel 9
video titled “<a href="https://channel9.msdn.com/blogs/OfficeDevPnP/PnP-Web-Cast-Getting-started-with-SharePoint-Framework">Getting started with SharePoint Framework</a>”.
In this video Vesa and Waldek go through the full development
cycle of a Client Web Part using the upcoming SharePoint
Framework.</p>
<figure class="align-center">
<img class="align-center" src="/images/posts/2016-05-17_developmentcycle.jpg" style="max-width: 720px" alt="Client WebPart Development Cycle" />
<figcaption style="text-align:center;">The development cycle of a Client WebPart</figcaption>
</figure>
<p>The development cycle utilizes multiple different web
development tools, which are actually already quite
popular in the general web development world, but might
be quite new to a traditional SharePoint developer.</p>
<h3 id="nodejs">Node.js</h3>
<p><a href="https://en.wikipedia.org/wiki/Node.js">Wikipedia</a> defines
Node.js as “an open-source, cross-platform runtime environment
for developing server-side Web applications.”</p>
<p>The SharePoint Framework and the development cycle utilizes
<a href="https://nodejs.org/">Node.js</a> to run tools like Gulp and for hosting your Client
WebPart during development. But these things will
be prepared for you by Microsoft and/or by the community.
So you don’t need to learn to write Node applications
to be able to create Client WebParts using the new SharePoint
Framework.</p>
<p>You will however be using the <a href="https://www.npmjs.com/">Node Package Manager (npm)</a>
when building your WebParts. Npm can be shortly described as the
“Nuget for Node.js”. You’ll use it to install the
SharePoint Framework itself and when you want to include some
open source JavaScript library in your own component.</p>
<p>Fortunately npm is quite easy to use. Mostly you will just
install packages with the <code class="highlighter-rouge">npm --save-dev packagename</code> command.</p>
<h3 id="yeoman">Yeoman</h3>
<p>If npm is the NuGet for Node.js, then the Visual Studio counterpart
for <a href="http://yeoman.io/">Yeoman</a> is the project template. When you select the
project template in in Visual Studio, Visual Studio will initialize
your project with all the initial files and settings. When developing
Client WebParts, you’ll initialize your project with Yeoman instead.
This is done by running something like <code class="highlighter-rouge">yo sharepoint</code> in
the command prompt.</p>
<p>The yeoman templates will be provided for you by Microsoft, so that
single command will be most likely everything you need to know
about Yeoman.</p>
<h3 id="gulp">Gulp</h3>
<p><a href="http://gulpjs.com/">Gulp</a> is a build process tool, which runs
on top of Node. It can be compared to MSBuild, which is used by
Visual Studio when you press <code class="highlighter-rouge">F5</code> in the IDE.</p>
<p>A gulp build process consists of different tasks which are writen
in JavaScript. Once these tasks are defined, you can execute them
with the shell command <code class="highlighter-rouge">gulp taskname</code>.</p>
<p>Yet again, the build tasks for Gulp are part of the SharePoint
Framework and you don’t have to write them yourself. The SharePoint
Framework will contain the necessary gulp tasks for building your
solution, starting the development-time webserver, packaging
the solution and uploading your bundled WebPart code to a CDN.</p>
<h3 id="typescript">TypeScript</h3>
<p>You can write your Client WebParts using JavaScript, but most people
will use <a href="https://www.typescriptlang.org/">TypeScript</a> instead.
TypeScript is a typed superset of JavaScript, which is transpiled
to JavaScript during the build process.</p>
<p>TypeScript has many advantages to plain JavaScript. For example
it makes it easier to write more robust code by supporting classes
and modules. It’s also transpiled with type checking, so you’ll
get compile-time information about typing mistakes etc…</p>
<p>In my opinion, TypeScript is the thing you should be studying
right now if you want to be prepared for the new SharePoint
Framework when it launches. The other things so far (Node, Yeoman, Gulp)
are mostly just tools you’ll be using to get your WebPart compiled.</p>
<h3 id="react-and-flux">React and Flux</h3>
<p>The final thing that is mentioned quite often with SharePoint
Framework is the <a href="https://facebook.github.io/react/">React-library</a>.
React is a UI library developed by Facebook and Instagram.
It helps you create a set of individual UI components (with
data-binding) and wire them together to create the full UI of your
WebPart.</p>
<p>The new SharePoint Framework will have support for UI components
written with React and lot’s of the examples and tutorials
about the SharePoint Framework will most likely be utilizing
React-library for rendering the user interface. But using React
is not required by the framework. You can use Knockout, Angular
or just plain TypeScript to render the UI instead.</p>
<p>I definitely encourage you to learn about React (and also
<a href="https://facebook.github.io/flux/">the Flux architecture model</a>)
when preparing for the new SharePoint Framework. But if you have
to choose between TypeScript and React, then I personally would
start with TypeScript!</p>Arto Kaitosaariarto.kaitosaari@gmail.comhttp://artokai.netVesa Juvonen and Waldek Mastykarz from the OfficeDev Patterns & Practises -team have released a new Channel 9 video titled “Getting started with SharePoint Framework”. In this video Vesa and Waldek go through the full development cycle of a Client Web Part using the upcoming SharePoint Framework. The development cycle of a Client WebPart