Loading...

Follow Waldek Mastykarz on Feedspot

Continue with Google
Continue with Facebook
or

Valid

I live in Slack but I'd love to be able to easily create Microsoft To-Do todos. It turns out, that there is a Flow for that.

About me

Everybody has their own way of managing tasks: sticky notes, paper notebook, OneNote, Outlook tasks or anyone out of the 20 million task management apps available out there. Personally, I'm an avid user of Wunderlist. I use it for everything from recurring tasks I need to do weekly or monthly to groceries. At work, however, I'm spending a lot of time in Slack. And so recently, I found myself writing down my todos in Slack and marking them as done with reactions. On one hand, it was very convenient because I was in Slack already so all my todos were right in front of me without having to switch the context. But with writing tasks as messages to yourself, it's all too easy to lose track of them over time as they move away from your timeline. So when recently, my colleague Mike introduced me to Microsoft To-Do I was all ears.

Microsoft To-Do, for all your tasks

I have tried Microsoft To-Do in the past, with little luck. When Wunderlist was acquired by Microsoft and Microsoft To-Do proposed as its replacement, I gave it a try. Unfortunately, migrating my Wunderlist tasks to To-Do failed and back then To-Do was missing some things that I was using in Wunderlist, so eventually, I stayed with Wunderlist.

Using To-Do for work today is a different story. Yes, it allows you to create tasks, set their due dates and arrange them in lists, but it also aggregates tasks from Outlook and Planner which is very helpful if you want to keep track of all your work in a single place.

I spend a lot of time in Slack. So for Microsoft To-Do to work for me, I need to be able to quickly create a task from Slack without having to leave my work and open To-Do. At the moment of writing this article, there is no Microsoft To-Do app for Slack. I'm fully aware of Slack's extensibility capabilities and I know I could create an app for it. But I was looking for something simpler. Something that wouldn't require me to learn Slack's APIs, app registrations or deployment processes. It turns out, there is an easy way of creating To-Do todos from Slack.

Create Microsoft To-Do todos from Slack with Flow

There are a number of ways in which you can extend Slack. One of them is to create a custom command. When you enter the command in Slack, it will call the specified URL passing whatever text you typed with the command along with the request. This makes a perfect use case for a Flow with an HTTP request trigger.

Create the Flow

Start, by creating a new Flow with an HTTP request trigger. Because you can't save a Flow with just a trigger, add an Initialize variable action setting its name to text, type to String and leaving the initial value empty. You'll use it later. Right now, all you need to do is to save the Flow, to get the HTTP request-trigger URL. You need the URL to define the custom Slack command which you will use to create todos from Slack. Once the command is in place, you will have all information, such as the payload structure and the security information, necessary to complete the Flow.

Create the Slack command

Typically, a custom Slack command requires building and registering a custom Slack application. There is however an alternative way, which isn't maybe as powerful, but which definitely is good enough for what we need.

Using the Slash Commands app created by Slack, you can easily set up custom commands that will allow you to integrate third-party services in Slack.

After installing the app in your workspace, add a new configuration.

What you should keep in mind, that whatever command you define, it will be available to everyone in your workspace. There is little harm in others creating a todo for you. On the contrary, it could introduce interesting collaboration scenarios. In the end, whatever name you choose, make it something unique and easy to memorize (you'll be using it to create todos).

After adding the configuration, paste the URL of your Flow's HTTP request trigger and set the method to POST.

Next, take note of the Token. You will need it to protect your Flow from being abused.

To finish the configuration, specify its display name and icon.

Because this command is meant for your personal use, you should consider removing it from the autocomplete list so that not everyone in your organization will see the command to add a todo for you, unless it's exactly what you want!

Before confirming the configuration, take note of the outgoing data of the payload.

This is the information that will be sent by Slack to your Flow every time you (or someone else) will use the command. As you can see, a part of the request is the user information which you could use to ensure that only you can add todos for yourself.

Finishing up the Flow

Now that you have the Slack command in place, the last piece left is to build the Flow that will receive the requests from Slack and create Microsoft To-Do todos on your behalf.

Start, by updating the HTTP request trigger and using the following JSON schema for the request body:

{
  "type": "object",
  "properties": {
    "$content-type": {
      "type": "string"
    },
    "$content": {
      "type": "string"
    },
    "$formdata": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "key",
          "value"
        ]
      }
    }
  }
}

This structure corresponds to the payload sent by Slack to the Flow.

Next, add a parallel branch and add to it a Response action with Status code 200 and a Body set to Creating todo... (or any other text you'd like to show in Slack directly after receiving the request).

This is necessary because executing the Flow could take longer than the 3000ms maximum set by Slack. If Slack doesn't receive a reply in that timeframe, it will assume that the requested failed. So for the setup to work correctly, you need to send an immediate response, while you're processing the request, validating its payload, creating a To-Do todo and sending a response back. For all this, Slack gives you 30 seconds, but the first response must be sent back within 3 seconds.

Go back to the main branch and initialize two more variables: response_url, which you will use to store the response URL to which to send the final result of adding a todo and token which will contain the security token sent by Slack to verify the origin of the request.

Next, you need to get the values of the three variables from the request data. Unfortunately, the data is set in an array rather than an object, so to get the value of the different variables, you have to iterate through the $formdata array.

For each of the three variables (text, response_url and token) add a condition block. Inside, compare the value of the key property with the variable name and if it matches is, set the value of the corresponding variable to the value of the current object.

With the three variables set, you're ready for the next step.

First, you should check if the token provided in the request, matches the secret from the Slack command.

If it doesn't, you should send an error message to the reply URL.

If it does, then you should obviously create the todo. Notice, how the Subject of the todo has been set to the text variable, which contains the text sent from Slack.

Finally, acknowledge the correct creation of a todo, by sending a message back to Slack:

If you've configured all elements correctly, after typing in Slack /mytodo Write a blog post about creating To-Do todos from Slack, you should see a response similar to the following:

Summary

Microsoft Flow offers great capabilities for automating tedious and repetitive tasks but also combining cloud services together to build powerful productivity solutions. Even if you live in Slack, like me, and want to connect it to other services in the Microsoft cloud, you can easily do it using Microsoft Flow, without overly complex development work.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

So the meeting is over and you're left with a bunch of action points that you need to put in Planner. Should you 'get clicking' or is there a better way?

Automation for the masses

Many applications in Office 365 expose APIs that make it possible to interact with them and automate some tasks. Unfortunately, these APIs are meant primarily for developers and even with the right skills, using them requires quite some effort. But what if all you need to do is to create some tasks in Planner and you don't have the time and money to start a project, write specs and review some custom code?

What macros are to Office clients, Microsoft Flow is to Office 365. It allows you to stitch the different services both inside and outside of the Microsoft cloud together with just a few clicks, without you having to worry about things like authentication or deployment. Assuming you can use Flow in your organization, you can easily automate many of your daily tasks.

Bulk-create Planner tasks with Flow

Getting back to our scenario. You attended a meeting, took some notes, you all agreed on some follow-up actions that you wrote down. To drive accountability, you want these actions to be in Planner. You have a Planner plan, you have the tasks, you just don't have tasks in Planner and creating them manually doesn't quite sound appealing. So instead you turn to Microsoft Flow.

Start, by creating a new Instant Flow.

In the dialog, provide the name for your Flow, and select Flow as the application from which it will be triggered.

In the newly created Flow, open the trigger and add a Text input. This will allow you to reuse the Flow in the future should you have more tasks to import.

Give the input a name and a description that will be displayed when you start the Flow.

Next, add a new Initialize variable step. Name the variable Tasks and set its type to Array. As its Value, set the following expression:

split(triggerBody()['text'], '
')

The expression reads the value from the text input you created previously, where each task to create in Planner is entered in a separate line. To use it further on, you need to break the input into separate lines. And to do it, you use the split function. The tricky part, especially if you've done some coding in that past, is that in order to split the input string per line, instead of using \n, \r\n  or something similar, you actually need to enter the actual line break, press the 'enter' key on your keyboard. Without it, you will end up with one line and create a single Planner task with all tasks as its title.

After this step, our tasks to create are stored in an array. So as a next step, we need to iterate through them using the Apply to each step and selecting the Tasks variable as the object to iterate on.

The last step left is to create the actual Planner task for each task from our input. To do this, inside the Apply to each step, add a Create a task Planner step. Sign in to Planner with your account and select the plan where to create the tasks. As the task's Title, select the Current item dynamic content. Finally, select the bucket where to put the tasks.

The last part left, is to test the Flow. From the top bar, select the Test button and perform the trigger action. In the Tasks input, specify the list of tasks to add to your plan.

If all went well, you should now see your tasks created in your plan.

Summary

Microsoft Flow is a perfect tool for automating all kinds of tedious and repetitive work in Office 365. Its simplicity and flexibility make it a perfect tool for everyone including seasoned developers who want to quickly get their job done.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Waldek Mastykarz by Waldek Mastykarz - 1M ago

What if you could manage your Office 365 tenant using a web terminal as you do with Azure?

A case for a command line on the web

Managing platforms using a web site is great. At least at first. You clearly see what you're doing and can change configuration with just a few clicks. Until you get to changing the same settings over and over again and clicking around becomes cumbersome.

Automating the configuration steps using a CLI or PowerShell cmdlets saves you time. But comes at a price. You need to install some prerequisites and regularly update the set of commands to manage the latest capabilities of your platform. But what if you could just use the command line and not have to worry about installing anything or keeping it up-to-date? What if it was all done for you?

Azure Cloud Shell

For a long time, Microsoft Azure has been offering, next to the web portal, the Cloud Shell - a command line on the web, for managing its assets and configuration. With a single click, you can open a command line in your browser and use the Azure CLI or PowerShell cmdlets to manage assets in your Azure subscription. It's always up-to-date and you can just use it. What if you could do the same for your Office 365 tenant?

Office 365 Cloud Shell

While there is no Cloud Shell available for Office 365, you can make one yourself. And there are two ways to do it.

Following examples are based on the Office 365 CLI. Theoretically, you could achieve similar results with the SPO- or PnP PowerShell.

Add Office 365 CLI to the Azure Cloud Shell

One way to create a cloud shell for your Office 365 tenant is by installing the Office 365 CLI in the Azure Cloud Shell. It requires very little setup and no maintenance, other than occasionally updating the CLI to install the latest version. If you have access to the Azure Cloud Shell, it's probably the easiest way to set it up. Scott Hoag has a nice article explaining the necessary steps to get it done.

Manage your Office 365 tenant from the Azure Cloud ShellBuild your own Office 365 Cloud Shell

If you don't have access to the Azure Cloud Shell, but you can get hold of some Azure resources, you can build your own Office 365 Cloud Shell.

Following is a high-level overview of the different building blocks and considerations involved in building your own Office 365 Cloud Shell. All code and setup instructions are available on GitHub.

Before you get started, here is the list of things you will need:

  • Azure subscription with an Azure Container Registry (ACR) and Azure Container Instances (ACI), to host the Docker container with Office 365 CLI
  • Office 365 tenant with app catalog, to deploy the SharePoint Framework web part
  • permissions on the Resource Group with ACR and ACI, to push the Docker image and deploy a container
  • tenant admin permissions in Office 365, to approve the permissions for the web part to communicate with the container
  • Docker installed locally, to build the Docker image
  • Node with npm and gulp installed locally, to build the SharePoint Framework solution
High-level architecture

Office 365 Cloud Shell consists of two pieces: a Docker container with the Office 365 CLI running on Azure Container Instances (1) and a SharePoint Framework web part which communicates with the container (2). The web part contains a web terminal. Each command you type is sent to ACI for execution (3). The output of each command is sent back to the web part to be displayed in the web terminal (4). The web part and ACI communicate with each other using a web socket to pass the input and output between each other efficiently.

Setup the Docker image

In the list of prerequisites, I mentioned using an Azure Container Registry. In fact, using the ACR is optional. Because the Docker image with the Office 365 CLI doesn't contain any sensitive information, you could just as well host it publicly on Docker Hub. If you prefer to keep the image private, start with creating the ACR so that you have a place to host your image. Don't forget to enable the admin account, so that you can log in to your registry and push the image to it using the docker cli on your machine.

Enable the admin account to push your local Docker image to ACR

The Docker image is simple and uses the standard Node.js Docker image as a base.

FROM node:10.15.3

RUN npm i -g @pnp/office365-cli
COPY ./loop.sh /usr/src/loop.sh
COPY ./.bashrc /root/.bashrc

CMD /bin/bash /usr/src/loop.sh

On top, it adds the Office 365 CLI and two files: loop.sh which is a bash script to keep the image running and a .bashrc file to define a custom command prompt. The loop.sh file is defined as the command of the image. It creates an infinite loop that keeps the container running. Without it, the container would start and stop immediately when ran on ACI.

Build the web part

The SharePoint Framework web part uses Xterm.js as the web terminal. It's a common package used among others by the Azure Cloud Shell or Visual Studio Code. Xterm offers rich configuration capabilities, but to keep things simple, the web part uses a basic configuration that just works:

this.term = new (xterm as any).Terminal({ convertEol: true });

The web part communicates with the Docker container using the Azure REST API. It starts, by executing the /bin/bash command:

this.azMgmtHttpClient
.post(`https://management.azure.com/subscriptions/${this.properties.subscription}/resourceGroups/${this.properties.resourceGroup}/providers/Microsoft.ContainerInstance/containerGroups/${this.properties.containerGroup}/containers/${this.properties.container}/exec?api-version=2018-10-01`, AadHttpClient.configurations.v1, {
  headers: {
    'content-type': 'application/json'
  },
  body: JSON.stringify({
    "command": "/bin/bash",
    "terminalSize": {
      "rows": this.term.rows,
      "cols": this.term.cols
    }
  })
})

By itself, the command doesn't do anything else than starting an instance of Bash in the container. ACI will, however, respond with a URL of the web socket that you can use to communicate with the container and seamlessly run Office 365 CLI commands!

Once the web part retrieved the response from the ACI, it opens up new web socket using the URL specified by the ACI:

this.socket = new WebSocket(res.webSocketUri);

Communication with the container through the socket is secured with a password which is a part of ACI's response. Once the socket has been opened, the password must be sent back to ACI or the socket will expire:

this.socket.onopen = (e) => {
  this.socket.send(res.password);
};

After instantiating the socket, the web part sets up an event handler, that passes the data sent by the container through the socket to the terminal to be displayed to the user:

this.socket.onmessage = (e) => {
  this.term.write(e.data);
};

Additionally, all user input in the terminal is passed through the socket:

this.term.on('data', (data) => {
  this.socket.send(data);
});

From now on, every command typed in Xterm will be sent to the Docker container running on ACI, executed and whatever output it produces will be sent back through the socket and displayed in Xterm.

Communicating with the Azure REST API

Azure REST API is secured by Azure AD and to call it, you have to provide a valid OAuth bearer token. Thanks to the AadHttpClient in the SharePoint Framework, obtaining an access token for an API secured with AAD is easy, assuming communicating with that API has been permitted by the tenant admin.

To request permissions to communicate with the REST API, the SharePoint Framework solution issues a permission request:

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    // ...
    "webApiPermissionRequests": [
      {
        "resource": "Windows Azure Service Management API",
        "scope": "user_impersonation"
      }
    ]
  },
  // ...
}

Before the web part can be used, the above permission must be granted by the tenant admin. If you want to increase the security of your tenant, you can change the solution to run in isolated mode where the permission will be granted only to this specific solution:

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    // ...
    "isDomainIsolated": true,
    // ...
  },
  // ...
}

That's all there is to it. Deploy the solution, grant the permission, start the container, add the web part to the page, provide the details to access your container and you're ready to go!

Office 365 CLI running in a SharePoint Framework web partConsiderations

The setup of the solution is relatively simple to help you get started. If you wanted to use this setup in production, here are some things that you should take into account.

Avoid unnecessary costs

In the current setup, the web part assumes that the container is running. Ideally, it would automatically start it when needed and stop when it's no longer used. Until then, you should stop the container when you no longer need it to avoid unnecessary costs.

Restrict access to the container

By default, everyone with access to your Azure subscription could connect to your Docker container with Office 365 CLI in it. With that, they could manage your tenant on your behalf. To prevent this from happening, you should limit the access to the container so that only you can access it. If your colleagues want to use the solution too, you should create dedicated containers for them that only they can access. After all, using these containers you're managing your Office 365 tenant with tenant admin permissions.

Log out in the CLI

When you no longer use the container, log out from Office 365 in the CLI. In case someone would get access to the container, it would have your refresh token stored which could be used to get access to your Office 365 tenant on your behalf.

Room for improvement

In case you're interested, here are some ideas about how you could improve the solution further.

Use isolated permissions

Instead of granting permissions to access the Azure REST API to all scripts in your tenant, you can grant them only to this particular solution. It will help you to improve the security posture of your intranet and avoid risks.

Automate managing the container

You could extend the solution, so that whenever you open the page with the web part on it, the web part automatically deploys a new container in ACI, configures its permissions so that only you access it and automatically connects to it. When you leave the page or have not used it for some time, the web part would automatically delete the container. This adjustment would also allow you to use a single web part for you and your colleagues.

That's all there is to it. Give it a try, tell me what you think and if you have other ideas to improve the setup, I'd love to hear them too!

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Waldek Mastykarz by Waldek Mastykarz - 1M ago

Many organizations want to enable Microsoft Flow for their users. But before they do, they need an answer to one question: how we do ensure that Flow won't expose our organization and its data to risks?

Read further on rencore.com

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

We've just published a new version of the Office 365 CLI with new commands for working with and managing Office 365 tenants and SharePoint Framework projects on any platform.

Manage Office 365 and SharePoint Framework projects on any platform

Office 365 CLI is a cross-platform CLI that allows you to manage various configuration settings of Office 365 and SharePoint Framework projects no matter which operating system or shell you use.

While building solutions for Office 365 expands beyond the Windows operating system, managing many of the platform settings is possible only through PowerShell on Windows. As more and more users work on non-Windows machines, it's inconvenient for them to have to use a Windows virtual machine to configure their tenants. With the Office 365 CLI, you can configure your tenant no matter which operating system you use. Additionally, using the Office 365 CLI, you can manage your SharePoint Framework projects.

May releases of the Office 365 CLI

May was a busy month with three releases of the Office 365 CLI. Netx to a regular, monthly release, we shipped two additional versions to support the updates to the SharePoint Framework for upgrading projects. But we have some cool new capabilities as well. Take a look.

Manage organizational news sites

As a part of the content management investments in SharePoint, Microsoft introduced the concepts of organizational news sites. The idea is, that administrators can designate the specific site as organizational news sites to surface news articles published on these sites to the rest of the organization.

For version 1.21.0 of the Office 365 CLI, David Calabro contributed a set of commands to manage organizational news sites.

To mark an existing site as an organizational news site, execute:

spo orgnewssite set --url https://contoso.sharepoint.com/sites/site1

To see the list of all sites marked as organizational news sites in your tenant, execute:

spo orgnewssite list

To unmark an organizational news site, execute:

spo orgnewssite remove --url https://contoso.sharepoint.com/sites/site1

For more information about managing organizational news sites using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Clone Microsoft Teams teams

More and more organizations use Microsoft Teams as their primary collaboration platform. Teams perfectly support the conversational aspects of collaboration and offer great capabilities for organizations to tailor them to their specific needs. If you'd like to create a new team, that resembles another team in your tenant, it would be a shame to have to do it manually. So to simplify the process, Joseph Velliah introduced in version 1.20.0 of the CLI a command to clone existing Microsoft Teams team.

To clone an existing Microsoft Teams team, execute:

graph teams clone --teamId 15d7a78e-fd77-4599-97a5-dbb6372846c5 --displayName "Library Assist" --partsToClone "apps,tabs,settings,channels,members"

For more information about managing Microsoft Teams teams using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Update Microsoft Teams team

As your organization uses Microsoft Teams more and more, the chances are that you will want to update their settings at some point, whether to change the description to better reflect the team's purpose or update its visibility or classification. In version 1.21.0, Rabia Williams introduced a command to update Microsoft Teams team's settings.

To update settings of a Microsoft Teams team, execute:

graph teams set --teamId '00000000-0000-0000-0000-000000000000' --visibility Private

For more information about managing Microsoft Teams teams using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Apply standard SharePoint themes

In the past already we introduced support for applying custom themes to modern SharePoint sites. For version v1.20.0 of the CLI, Anoop Tatti extended the spo theme apply command with support for standard SharePoint themes which is useful if you want to revert the look and feel of a particular site to a standard theme.

To apply a standard theme to your site, execute:

spo theme apply --name Blue --webUrl https://contoso.sharepoint.com/sites/project-x --sharePointTheme

For more information about working with modern themes using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Remove content types

If your organization has been using SharePoint for a long time, there is a chance that you have some unused content types that clutter the UI and complicate content management for users. To help you clean them up, David Calabro contributed to version 1.20.0 a command to remove content types.

To remove a content type, execute:

spo contenttype remove --id "0x01007926A45D687BA842B947286090B8F67D" --webUrl https://contoso.sharepoint.com

For more information about managing content types using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

List Office 365 Groups and Microsoft Teams teams users

In the past already, we included support for listing users of Microsoft Teams teams which is invaluable for ongoing management and reporting the usage of teams in your organization. In version 1.20.0 Albert-Jan Schot extended the existing command to support retrieving users of both Microsoft Teams and Office 365 Groups.

To retrieve users of the specified Office 365 Group, execute:

graph o365group user list --groupId '00000000-0000-0000-0000-000000000000'

For more information about managing Microsoft Teams and Office 365 Groups using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Contributors

This release wouldn't be possible without the help of (in alphabetical order) Stefan Bauer, David Calabro, Velin Georgiev, Albert-Jan Schot, Anoop Tatti, Joseph Velliah and Rabia Williams. Thank you all for the time you chose to spend on the Office 365 CLI and your help to advance it!

Work in progress

Here are some things that we're currently working on.

More commands, what else

Office 365 is evolving and new capabilities are being released every day. With the Office 365 CLI, we aim to help you manage your tenant on any platform in a consistent way, no matter which part of Office 365 you interact with. While we keep adding new commands to the Office 365 CLI each release, we still barely scratched the surface with what's possible in Office 365. In the upcoming versions of the Office 365 CLI, you can expect us to add more commands across the different workloads in Office 365.

To make it easier for you to see how the CLI commands compare to the different PowerShell cmdlets, we've extended the comparison sheet with cmdlets for Flow, PowerApps and Teams.

Future improvements

CLI exists a year and a half now. Over time we picked up some things that we think we could improve. We've just refactored web requests and simplified logging in to Office 365. Next, we'll work on aligning the names of the commands and their arguments to be consistent and more intuitive. If you use the Office 365 CLI, we'd love to hear from you if there is anything else that we should consider.

Try it today

Get the latest release of the Office 365 CLI from npm by executing in the command line:

npm i -g @pnp/office365-cli

If you need more help getting started or want more details about the commands, the architecture or the project, go to aka.ms/o365cli. If you see any room for improvement, please, don't hesitate to reach out to us either on GitHub, on twitter with the #office365cli hashtag or on gitter.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Azure Container Instances are the easiest way to host Docker containers in the cloud. Until you want them to do... nothing.

Running containers on Azure Container Instances

Azure Container Instances (ACI) is one of the services on Microsoft Azure that you can just start using with little to no overhead. You point it to a Docker image that you want to run, which can be either public or private, optionally configure a few details and off you go.

ACI takes away the complexity of setting up and operating Docker infrastructure and allows you to focus on your job. Sure, if you have a complex SaaS solution that consists of multiple containers that depend on one another, it might not be the right solution for you, but if all you need is a way to run a specific task, either on-demand or on a schedule, you will appreciate the simplicity of ACI.

ACI can be used for anything: from hosting web sites, APIs and web servers to running tasks that work with Azure Storage Queues, Table- and Blob Storage. As long as you have a Docker image that does something, you can start it in ACI. But what if you have an image that doesn't do anything?

Running idle containers on ACI

Recently I've been researching the idea of running the Office 365 CLI in a Docker container that I could communicate with from a SharePoint Framework web part. I would run the container with the CLI in the cloud which would be sitting idle, waiting for me to send a command to it. ACI was the perfect candidate for the job for its ease of setup and use.

Typically, when you build a Docker image with which you want to interact, you specify /bin/bash as the image's CMD. After starting the container with the -it arguments, your terminal is linked to the container and you can directly execute commands inside the container. Unfortunately, when you run such image in ACI, the container starts and stop immediately. Apparently, /bin/bash is not a process that keeps running when running the container on ACI.

So instead, if you need your container to run idly waiting for your input, you need to keep it busy doing something, even if it's nothing. To do that, include in your Docker image the following bash script:

#!/bin/bash
while :
do
  sleep 1
done

Then, change CMD in your image to CMD /bin/bash /usr/src/loop.sh so that at startup, the container will execute the script which will never finish.

Once your container starts, you can execute commands in it using the Azure CLI or the Azure REST API. Oh, and don't forget to stop your container if you no longer need it to avoid incurring unnecessary costs.

Photo by frank mckenna

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Waldek Mastykarz by Waldek Mastykarz - 1M ago

When building your application, should you use REST or an SDK? The choice seems almost religious but often one makes more sense than the other.

Gone are the days when we were building self-contained applications. Nowadays, they are typically thin clients connecting to data stored elsewhere. This is especially the case for applications for Office 365. There is abundance of data stored in Office 365. All you need to do is to reach out, and there are two common ways of doing it.

Connect to the Microsoft cloud using REST

With a few exceptions, all services in the Microsoft cloud offer a REST API for applications to communicate with them and interact with their data. Using REST is simple and comes down to composing a request with the correct URL, headers and optionally a body.

No matter if you’re using JavaScript, C#, Python or golang: if you can issue a web request, you can communicate with the Microsoft cloud.

Connect to the Microsoft cloud using an SDK

Another way of communicating with services in the Microsoft cloud is by using an SDK. SDK is an abstraction later on top of the web API exposed by the particular service. Typically it abstracts away complexities such as authentication allowing you to focus on your work.

SDK is built for a specific stack which has its pros and cons. Well get to them in a minute.

SDK vs REST on the Microsoft Cloud

So with the two options available, which one should you use? Many developers swear by one or the other. Theoretically, both methods have their strengths and advantages. But in practice one of them makes more sense. Have a look for yourself.

It’s just text

REST requests are web requests to a specific URL with a set of headers and sometimes a body. But the URL is just a piece of text that can quickly get complex the moment you start using OData or similar kinds of expressions. It’s easy to make a typo or incorrectly escape an argument. Typo in a string is not a compiler error and you won’t know about it until you run your code. You could catch it with a unit test, unless you copied the URL from the code to the test along with the typo.

Using an SDK means writing code. SDK has types guiding you to use the right types of attributes, intellisense to help you be more efficient and any typo, unless in resource’s name, will be caught by the compiler.

It’s verbose and it’s good and bad

You could think of REST as a low-level API for the web. There is nothing between you and the service and you need to tell it exactly what you want it to do.

On one hand it’s a good thing. Lack of abstraction helps you to understand how the API works and when things break you get the raw response from the API which could contain some details that could help you find out what’s wrong.

But the verbosity has its price. The moment you start using more complex capabilities such as batching requests or properly handling throttling or even authentication, it will take significant effort to get everything right. A good SDK, does all of this, and more, for you, allowing you to focus on the job.

Not so cutting edge

Different product teams take different approach to releasing their SDKs. Some autogenerate them from the API, some build it manually thinking about scenarios that developers want to achieve. No matter the approach, it could be that the API you want to use is not yet available in the SDK. In such case you can postpone building the functionality, which rarely is a viable option, revert to using REST or build your own wrapper that you’ll replace after the SDK has been updated. SDKs are a point-in-time representation of the corresponding API. When things change in the API, you need a new SDK.

None of this is an issue when using REST. If the API is exposed, you can call it. It’s that simple.

But I’m coding in X

SDKs are built for the particular technology stack. Taking SharePoint as an example, there is an official SDK for .Net, and community-driven SDK for JavaScript. If these languages are your development stack, you’re in good hands. But if you’re building things in .net core, golang, Python or native iOS apps, you’re out of luck and will either need to defer to using REST or build your own SDK.

When you’re used to working with REST, you can more easily switch between the different technology stacks. As long as you know how to issue a web request on the particular stack, you will be good to go and you will be able to benefit of your existing experience.

On a related note, if you switch between different technology stacks, you might prefer to use REST. SDKs often strive to fit paradigms of the particular stack, but there might be implementation nuances between the different stacks that could end up confusing you. REST on the other hand, is just REST no matter if you’re coding for iOS or in C#.

Some examples

Theory aside, let's have a look at a few examples that could justify using one approach over the other.

You build SharePoint solutions for organizations on the Microsoft stack, and that's it

In your job, you and your colleagues almost exclusively focus on building solutions that integrate with SharePoint. Your customers are organizations with significant investments in Microsoft technology. In the past, you would typically build Farm solutions and communicate with SharePoint using the server-side object model. Nowadays, you use SharePoint Framework to build the UI and remote code for things like long-running operations or provisioning.

With such specific focus, you would get the most benefit from using the PnP Core Component (or PnP CSOM as some refer to it) for managed code and PnPjs for client-side communication with SharePoint. The PnP Core Component offers you a lot of convenience methods for remotely communicating with SharePoint. Using the fluent API of PnPjs you will be able to more easily communicate with SharePoint and use complex features such as request batching without pulling your hair out.

You build solutions for Office 365 that also include some Azure workloads

Your customers are in the cloud and you build for them productivity solutions that combine the information from the different Office 365 services. Processing and maybe even storage of some of the data is offloaded to Azure. Your solutions are built on the Microsoft stack.

Microsoft Graph does a great job bringing together information from the different services in Office 365. For SharePoint scenarios that go beyond accessing files or sites, you need to use SharePoint APIs. To communicate with both APIs, using the SDK will give you the best experience and efficiency. Depending on which services in Azure you're using exactly, there should be a .NET SDK available for it. In some cases the latest capabilities are not available in the SDK so if you need to use them, you would need to defer to using REST.

You're an ISV and you build a SaaS solution that integrates with Office 365

Being an ISV you pick a cloud-native stack that can support your SaaS solution. You go either with Docker containers or fully serverless, most likely on a Linux runtime.

For communicating with Office 365, you can use one of the different Microsoft Graph SDKs available for the most popular development platforms. For interacting with SharePoint the available options vary. If you built your solution on Node.js, you can use the community-driven PnPjs SDK. If you're using .net core or other stack, you're out of luck as at this moment there are no SDKs available and you would need to use REST.

You're building different solutions on different platforms

Your organization builds a variety of solutions that range from SharePoint Framework web parts to mobile apps for iOS and Android and everything in between. You and your colleagues have a broad skillset and regularly switch between the type of applications that your organization is building.

Each SDK comes with its own way of communicating with the server-side API. To prevent yourself from having to fully understand the intricacies of each and every SDK on each and every platform and increase the reusability of your code, you will likely get more benefit from using REST than the different SDKs.

Rule of thumb

If you focus on working with a specific technology stack, you should default to using the dedicated SDK. It will help you to work effectively and avoid trivial, yet hard to spot, errors. If you're typically experimenting with different technologies or build solutions that span multiple services and you want to reuse code across all of them, REST might be more beneficial to you.

Photo by Markus Spiske on Unsplash

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Waldek Mastykarz by Waldek Mastykarz - 2M ago

We've just published a new version of the Office 365 CLI with new commands for working with and managing Office 365 tenants and SharePoint Framework projects on any platform.

Manage Office 365 and SharePoint Framework projects on any platform

Office 365 CLI is a cross-platform CLI that allows you to manage various configuration settings of Office 365 and SharePoint Framework projects no matter which operating system or shell you use.

While building solutions for Office 365 expands beyond the Windows operating system, managing many of the platform settings is possible only through PowerShell on Windows. As more and more users work on non-Windows machines, it's inconvenient for them to have to use a Windows virtual machine to configure their tenants. With the Office 365 CLI you can configure your tenant no matter which operating system you use. Additionally, using the Office 365 CLI, you can manage your SharePoint Framework projects.

New version of Office 365 CLI - v1.18.0

Following our monthly release cadence, we've released a new version of the Office 365 CLI with some new capabilities.

Enable communication site features on the root site

As a part of the investments in modern SharePoint experiences, Microsoft announced last year the ability to turn the root site of an Office 365 tenant into a modern communication site. For many users, intranet starts with a landing page and you wouldn't expect anything less from a modern intranet than a modern landing page.

Just recently, Microsoft started releasing the preview of this capability to tenants across the world. By enabling it, you in fact activate communication site features on the existing root site. At the moment of writing this article, this capability works only on the root site collection but in the future it could potentially work on other sites as well.

To enable communication site features on the root site of your tenant, execute:

spo site commsite enable --url https://contoso.sharepoint.com

For more information about working with modern sites using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Renew Office 365 Group's expiration

Office 365 gives administrators the ability to configure expiration policy on Office 365 Groups and Microsoft Teams. Using these policies, administrators can automate removing stale groups. If the particular group is still needed, group owners can choose to extend its expiration period.

For this release of the Office 365 CLI, Rabia Williams contributed a command to extend the expiration of an Office 365 Group/Microsoft Team.

To extend the expiration of an Office 365 Group, execute:

graph o365group renew --id 28beab62-7540-4db1-a23f-29a6018a3848

For more information about managing Office 365 Groups and Microsoft Teams using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Remove users from the specified Office 365 Group/Microsoft Teams

As your organization starts using Office 365 Groups and Microsoft Teams more and more, you will likely need to bulk-manage their users, and it's not something you'd want to do manually. To help you automate managing users of your Microsoft Teams and Office 365 Groups, Albert-Jan Schot contributed a command to remove users from Office 365 Groups and Microsoft Teams.

To remove a user from an Office 365 Groups or Microsoft Team, execute:

graph o365group user remove --groupId '00000000-0000-0000-0000-000000000000' --userName 'anne.matthews@contoso.onmicrosoft.com'

For more information about managing Office 365 Groups and Microsoft Teams users using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Control footer visibility in modern communication sites

Another feature that is a part of the modern SharePoint UX investments, is the footer, that can be displayed on modern communication sites. Using the footer, you can offer your colleagues easy access to frequent locations and applications in your organization.

For this release of the Office 365 CLI, Rajesh Sitaraman contributed a command that allows you to choose if the footer should be visible or not on the particular site.

To show the footer, execute:

spo web set --webUrl https://contoso.sharepoint.com/sites/team-a --footerEnabled true

For more information about managing site settings using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Improved communication with Office 365

At the heart of the Office 365 CLI are web requests to the APIs of the different services in the Office 365. In this version of the Office 365 CLI, we've done a major refactoring of web requests.

First of all, we've added support for gzip compression which should decrease the amount of data sent over the wire. You should notice the difference especially when working with commands that return many results or send large payloads to Office 365.

Another refactoring that we've done, is adding support for properly handling throttling. This is crucial when you want to use the Office 365 CLI reliably in automation scenarios and want it to properly handle situations where the target Office 365 tenant is under heavy load and requires all applications to decrease their traffic.

Big thanks to Velin Georgiev for helping with this core refactoring that significantly improves the Office 365 CLI.

Contributors

This release wouldn't be possible without the help of (in alphabetical order) Velin Georgiev, Albert-Jan Schot, Rajesh Sitaraman and Rabia Williams. Thank you all for the time you chose to spend on the Office 365 CLI and your help to advance it!

Work in progress

Here are some things that we're currently working on.

More commands, what else

Office 365 is evolving and new capabilities are being released every day. With the Office 365 CLI we aim to help you manage your tenant on any platform in a consistent way, no matter which part of Office 365 you interact with. While we keep adding new commands to the Office 365 CLI each release, we still barely scratched the surface with what's possible in Office 365. In the upcoming versions of the Office 365 CLI, you can expect us to add more commands across the different workloads in Office 365.

To make it easier for you to see how the CLI commands compare to the different PowerShell cmdlets, we've extended the comparison sheet with cmdlets for Flow, PowerApps and Teams.

Future improvements

CLI exists a year and a half now. Over time we picked up some things that we think we could improve. We've just refactored web requests and we've started working on simplifying logging in to Office 365. Next, we'll work on aligning the names of the commands and there arguments to be consistent and more intuitive. If you use the Office 365 CLI, we'd love to hear from you if there is anything else that we should consider.

Try it today

Get the latest release of the Office 365 CLI from npm by executing in the command line:

npm i -g @pnp/office365-cli

If you need more help getting started or want more details about the commands, the architecture or the project, go to aka.ms/o365cli. If you see any room for improvement, please, don't hesitate to reach out to us either on GitHub, on twitter with the #office365cli hashtag or on gitter.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Waldek Mastykarz by Waldek Mastykarz - 3M ago

We've just published a new version of the Office 365 CLI with new commands for working with and managing Office 365 tenants and SharePoint Framework projects on any platform.

Manage Office 365 and SharePoint Framework projects on any platform

Office 365 CLI is a cross-platform CLI that allows you to manage various configuration settings of Office 365 and SharePoint Framework projects no matter which operating system or shell you use.

While building solutions for Office 365 expands beyond the Windows operating system, managing many of the platform settings is possible only through PowerShell on Windows. As more and more users work on non-Windows machines, it's inconvenient for them to have to use a Windows virtual machine to configure their tenants. With the Office 365 CLI you can configure your tenant no matter which operating system you use. Additionally, using the Office 365 CLI, you can manage your SharePoint Framework projects.

New version of Office 365 CLI - v1.16.0

Following our monthly release cadence, we've released a new version of the Office 365 CLI with some new capabilities.

Manage Microsoft Teams

More and more organizations use Microsoft Teams to facilitate communication and collaboration. As Teams are gaining popularity, you might need to manage some of their aspects like configurations or members in an automated way. In this release of the Office 365 CLI, we continued extending the list of commands for managing Microsoft Teams and their settings.

Create teams

To offer your users a more complete experience and better support their collaboration needs, you might want to create teams pre-configured with specific channels, tabs and apps. It all starts with creating the team. For this version of the Office 365 CLI, Balamurugan Kailasam contributed a command to create Microsoft Teams teams.

To create a team, execute:

graph teams add --name 'Architecture' --description 'Architecture Discussion'

In the future versions of the CLI, you can expect to be able to create Teams from a template. For now, you'd need to execute the different commands yourself.

Uninstall app from the particular team

As your teams evolve, their need might change and applications built and used previously, might no longer be sufficient. To simplify removing applications that are no longer needed, Vardhaman Deshpande contributed a command to uninstall applications from Microsoft Teams teams.

To uninstall the particular application from the given Microsoft Teams team, execute:

graph teams app uninstall --appId YzUyN2E0NzAtYTg4Mi00ODFjLTk4MWMtZWU2ZWZhYmE4NWM3IyM0ZDFlYTA0Ny1mMTk2LTQ1MGQtYjJlOS0wZDI4NTViYTA1YTY= --teamId 2609af39-7775-4f94-a3dc-0dd67657e900
Update channel settings

When configuring teams, it's hard to predict upfront how the particular group of people will work together. It's not uncommon for the originally created channels to be insufficient or to change over time. And if you have a number of teams created in the same way, updating all of them would quickly become tedious. To simplify updating channels, Rabia Williams contributed a command that you can use to update title and description of a channel.

To set a new name and description for a channel in a Microsoft Teams team, execute:

graph teams channel set --teamId "00000000-0000-0000-0000-000000000000" --channelName Reviews --newChannelName Projects --description "Channel for new projects"
Update guest settings

Using Microsoft Teams, your colleagues can collaborate with external users outside of your organization. To ensure that these external users work with Teams according to your organization's policies, you can configure what these external users are allowed to do in the Teams they can access.

To allow external users to create and update channels, execute:

graph teams guestsettings set --teamId '00000000-0000-0000-0000-000000000000' --allowCreateUpdateChannels true

To disallow guests to delete channels, execute:

graph teams guestsettings set --teamId '00000000-0000-0000-0000-000000000000' --allowDeleteChannels false
List team's tabs

Microsoft Teams offer your organization rich collaboration capabilities that can be further extended with custom applications. To learn more about which applications are used in your organization, you could regularly retrieve the list of tabs used in teams in your tenant. For this release of the Office 365 CLI, Thomy Gölles contributed a command to list all tabs used in the given channel of the particular Microsoft Teams team.

To list tabs used in the particular Microsoft Teams team's channel, execute:

graph teams tab list --teamId 00000000-0000-0000-0000-000000000000 --channelId 19:00000000000000000000000000000000@thread.skype

For more information about working with Microsoft Teams teams using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Add users to Office 365 Groups and Microsoft Teams

If your organization uses Microsoft Teams or Office 365 Groups heavily, you might find yourself in a situation where you need to add users to newly created groups/teams. If it's more than a handful of groups and users, the process will quickly become cumbersome. The good news is that you can automate it using the command contributed by Albert-Jan Schot.

To add a user to an Office 365 Group, execute:

graph o365group user add --groupId '00000000-0000-0000-0000-000000000000' --userName 'anne.matthews@contoso.onmicrosoft.com'

The same way, you can add a user to a team:

graph teams user add --teamId '00000000-0000-0000-0000-000000000000' --userName 'anne.matthews@contoso.onmicrosoft.com'

Whether you use the groupId or the teamId option, their interchangeable and the result is the same.

For more information about working with Microsoft Teams teams and Office 365 Groups using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Declare list items as records

Many organizations use SharePoint as a record management system. For this version of the Office 365 CLI, Arnie Raju contributed a command that allows you to declare a list item as a record. This command is invaluable if you want to automate the process or have a backlog of items to be defined as records in your environment.

To declare a list item as a record, execute:

spo listitem record declare --webUrl https://contoso.sharepoint.com/sites/project-x --listTitle "Demo List" --id 1

For more information about managing records and their settings using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Create schema extensions

Using the Microsoft Graph, developers can store additional information about objects like users or messages. Developers can define the shape of this additional pieces of information through schema extensions. For this release of the Office 365 CLI, Yannick Plenevaux contributed a command to add a new schema extension to your environment.

To add a schema extension, execute:

graph schemaextension add --id MySchemaExtension --description "My schema extension" --targetTypes Group --owner 62375ab9-6b52-47ed-826b-58e47e0e304b --properties `"[{""name"":""myProp1"",""type"":""Integer""},{""name"":""myProp2"",""type"":""String""}]`

For more information about working with schema extensions using the Office 365 CLI, see the documentation at aka.ms/o365-cli.

Bug fixes and improvements

Next to a number of new commands, this release of the Office 365 CLI contains a number of bug fixes and improvements.

Fixed adding sections and configuring headers of modern pages

The most important fixes in this release are related to recent changes in the modern pages' DOM, which broke creating sections, configuring page headers and adding web parts. This version of the Office 365 CLI contains fixes for creating sections and configuring headers of modern pages. Fix for adding web parts will be included in the next version of the CLI.

Showing apps installed in the particular Microsoft Teams team

In the previous version of the Office 365 CLI, we introduced the ability to list Microsoft Teams apps available in your organization. In this release, we've extended the existing command to allow retrieving information about apps installed in the particular Microsoft Teams team.

To list apps installed in the particular Microsoft Teams team, execute:

graph teams app list --teamId 6f6fd3f7-9ba5-4488-bbe6-a789004d0d55
Groupify your sites

If you've been using Office 365 for a while now, you might have a number of classic Team Sites. Unlike modern Team Sites, these classic site collections are nothing but SharePoint sites and don't integrate with other services in Office 365. To prevent you from having to recreate these sites, Microsoft allows you to connect these classic sites to Office 365 Groups and other Office 365 services like Planner or Calendar.

In the previous version of the Office 365 CLI we've introduced the spo site office365group set command which connects a classic site collection to Office 365 Groups. To make the command easier discoverable, we've added the spo site groupify alias, following the commonly used name for the process of connecting existing Team Sites to Office 365 Groups.

Contributors

This release wouldn't be possible without the help of (in alphabetical order) Hugo Bernier, Vardhaman Deshpande, Velin Georgiev, Thomas Gölles, Balamurugan Kailasam, Yannick Plenevaux, Arnie Raju, Albert-Jan Schot and Rabia Williams. Thank you all for the time you chose to spend on the Office 365 CLI and your help to advance it!

Work in progress

Here are some things that we're currently working on.

More commands, what else

Office 365 is evolving and new capabilities are being released every day. With the Office 365 CLI we aim to help you manage your tenant on any platform in a consistent way, no matter which part of Office 365 you interact with. While we keep adding new commands to the Office 365 CLI each release, we still barely scratched the surface with what's possible in Office 365. In the upcoming versions of the Office 365 CLI, you can expect us to add more commands across the different workloads in Office 365.

To make it easier for you to see how the CLI commands compare to the different PowerShell cmdlets, we've extended the comparison sheet with cmdlets for Flow, PowerApps and Teams.

Future improvements

CLI exists over a year now. Over time we picked up some things that we think we could improve. We started to keep track of the ideas that we have for what we could do better to make the CLI easier to use. So far, we've decided to implement a few of them. If you use the Office 365 CLI, we'd love to hear from you if these improvements would be worth the effort and if there is anything else that we haven't considered.

Try it today

Get the latest release of the Office 365 CLI from npm by executing in the command line:

npm i -g @pnp/office365-cli

If you need more help getting started or want more details about the commands, the architecture or the project, go to aka.ms/o365cli. If you see any room for improvement, please, don't hesitate to reach out to us either on GitHub, on twitter with the #office365cli hashtag or on gitter.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

So you created a new modern team site and you can't find it or any information stored inside through SharePoint Search.

Modern Team Sites

Modern Team Sites are the default experience to support collaboration in Office 365. They extend the basic team collaboration features with additional capabilities such as e-mail, calendar or tasks. All of that is exposed through a modern UI that makes the features easy to use.

When creating modern Team Sites, you can choose if the information stored in the site should be shared with everyone or only the site's members.

Creating modern Team Sites

And it's the private Team Sites, that can cause some confusion.

Inconvenient modern Team Sites and search

While the site is being created, SharePoint asks you if you want to add owners and members to the site.

Adding owners and members: more than meets the eye

If you don't specify any users, you will not be able to retrieve neither the site nor any information stored inside through SharePoint Search!

"Nothing here matches your search"

If you check the crawl log, you would see that the site and its data have been indexed correctly. And yet, none of it will be returned when you try searching for it.

The missing ingredient

It turns out, that while creating modern Team Site, by default no users are set as owners or members. Because search results are security trimmed, no information about the site is returned. To fix the problem, you should specify the current user as one of the site owners.

If you're creating modern Team Sites programmatically using the Office 365 CLI, after creating the site, you need to set its owners:

# create modern Team Site
o365 spo site add --type TeamSite --alias teamsite --title Team Site

# set site owners
o365 spo site classic set --url https://contoso.sharepoint.com/sites/teamsite --owners admin@contoso.com
Despite its name, the spo site classic set command works with both classic and modern site collections.

After setting site owners, you will be able to retrieve the information from the modern Team Site using search as expected.

In case you don't see changes quickly enough, request reindexing the site, either through search settings or using the spo web reindex Office 365 CLI command.

Big thanks to Mikael Svenson for being the second pair of eyes and helping me find out what was wrong.

Read Full Article

Read for later

Articles marked as Favorite are saved for later viewing.
close
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview