How to generate a ClientContext from SiteId in SharePoint Online? - sharepoint-online

I have a SiteId and I want to generate a ClientContext to fetch all the groups of that particular site. But I am not able to find a way to generate a ClientContext from the SiteId same we do in SharePoint on-premises.
Is there a way to generate a ClientContext from SiteId in SharePoint Online or we need the URL only?
I want to achieve something like this:
using(var context = new ClientContext(new GUId(siteId))
{
//TODO
}

You can get your ClientContext in two steps:
search the site by its ID using the search API
create a client context using the site's URL
Here's some PowerShell doing exactly this. I'm using the PnP Cmdlets out of convenience, similar results can also be achieved using plain CSOM.
# this is your site's ID
$siteId = "a20d2341-1b4f-47ed-8180-24a5c31adfa9"
# basically any known site URL - the root is probably fine
$anySiteUrl = "https://<yourtenant>.sharepoint.com"
$credential = Get-Credential
Connect-PnPOnline –Url $anySiteUrl –Credentials $credential
# search for site by ID
$site = Submit-PnPSearchQuery -Query "SiteID:$siteId AND ContentClass=STS_Site"
if ($site.ResultRows.Count -eq 1)
{
# URL to use for "real" connection
$siteUrl = $site.ResultRows[0].Path
Connect-PnPOnline –Url $siteUrl –Credentials $credential
$currentSite = Get-PnPSite
# and there is your ClientContext
$ctx = Get-PnPContext
$web = $currentSite.RootWeb
$ctx.Load($web)
$ctx.Load($web.SiteGroups)
$ctx.ExecuteQuery()
# here are your groups
$web.SiteGroups
}
(Note: you must install the SharePointPnPPowerShellOnline PowerShell module for this code to run.)

Related

How can I change the View of a SharePoint Online folder programmatically?

I have a powershell script that creates Folders in SharePoint online.
I'm using Add-PnPFolder to do so.
By default the new folder view is sorted by Name.
I wish to change the default view so that it is sorted by the field Created.
Manually this can easily be done. But programmatically I have no clue how to change the view of a PnPFolder.
Here's the part of the code where I create the folder...
Connect-PnPOnline -ClientId $SPO_AppId -ClientSecret $SPO_AppSecret -Url $siteUrl
$connection = Get-PnPConnection
if ($connection)
{
Add-PnPFolder -Folder /Team/Acquisition -Name Approvals -Connection $connection
}
Get-PnPView only works on PnPLists, not on PnPFolders unfortunately.
The order of the items displayed in view is configured in the view property, not in item property.
Code example to change the sorting property of a view:
$Context = Get-PnPContext
$view=Get-PnPView -List "Documents" -Identity "2B0E08F9-39AC-4553-9343-FDDF3551F77A"
$Context.Load($View)
$Context.ExecuteQuery()
$Query= "<OrderBy><FieldRef Name='Created' /></OrderBy>"
$View.ViewQuery = $ViewQuery
$View.ViewQuery = $QUERY
$View.Update()
$Context.ExecuteQuery()

Is there a nicer way to export Azure DevOps project's Templates (fields and states)?

I use 'witadmin listfields' command for whole collection, but wondering if I could scale fields/states just to a single project?
The reason behind this: sometimes I migrate TFS project to AzureDevOps existing project. And collecting data about fields takes a lot of manual work. Wondering about the automation of this process...
Many thanks!
You can check out the rest api to get the fields/states of a project. See below:
Work Item Types Field - List
GET https://{instance}/{collection}/{project}/_apis/wit/workitemtypes/{type}/fields?api-version=4.1
Work Item Type States - List
GET https://{instance}/{collection}/{project}/_apis/wit/workitemtypes/{type}/states?api-version=4.1-preview.1
For below example, call above rest apis in powershell scripts:
[string]$userName = 'domain\username'
[string]$userPassword = 'password'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credOject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)
$uri = "http://{instance}/{collection}/{project}/_apis/wit/workitemtypes/Bug/fields?api-version=4.1"
$invRestMethParams = #{
Credential = $credOject
Uri = $uri
Method = 'Get'
ContentType = 'application/json'
}
Invoke-RestMethod #invRestMethParams

Multi-tenancy in Power BI Embedded

I have a multi-tenant web application and I am using a database per tenant approach. The web application will also use Power BI Embedded to show reports based on the data for that particular tenant and all reports for each tenant will have the same format but the data source will be different.
From what I've seen, there is not straightforward way to implement multi-tenancy in Power BI, such as passing the data source as parameter. I managed to find two ways how to make Power BI embedded multi-tenant. Either use row-level security which would mean that I need to have a single data warehouse for all the tenant's data, and this is not an option for me. The other option would be having a workspace per tenant.
For the second option I would have a template workspace from which a copy will be created for each new tenant. This tutorial here describes how to do it: https://powerbi.microsoft.com/fr-fr/blog/duplicate-workspaces-using-the-power-bi-rest-apis-a-step-by-step-tutorial/ .
Can the same thing be done through the Power BI C# SDK? I would also need to change the data source used per workspace. How can I do this for all reports in my workspace?
Finally, has someone discovered an easier way how to implement multi-tenancy with Power BI embedded or is this it?
It depends on your data source type (SQL Server, SSAS, CSV files, etc.) and data connectivity mode (import, direct query, etc.). If you can use parameters, then one of your options is to allow the newly cloned report to switch it's data source itself by using connection specific parameters. To do this, open Power Query Editor by clicking Edit Queries and in Manage Parameters define two new text parameters, lets name them ServerName and DatabaseName:
Set their current values to point to one of your data sources, e.g. SQLSERVER2016 and AdventureWorks2016. Then right click your query in the report and open Advanced Editor. Find the server name and database name in the M code:
and replace them with the parameters defined above, so the M code will look like this:
Now you can close and apply changes and your report should work as before. But now when you want to change the data source, do it using Edit Parameters:
and change the server and/or database name to point to the other data source, that you want to use for your report:
After changing parameter values, Power BI Desktop will ask you to apply the changes and reload the data from the new data source. To change the parameter values (i.e. the data source) of a report published in Power BI Service, go to dataset's settings and enter new server and/or database name (check the gateway settings too, if this is on-premise data source):
After changing the data source, refresh your dataset to get the data from the new data source. With Power BI Pro account you can do this 8 times per 24 hours, while if the dataset is in a dedicated capacity, this limit is raised to 48 times per 24 hours.
To do this programatically, use Update Parameters / Update Parameters In Group and Refresh Dataset / Refresh Dataset In Group REST API calls. For example, you can do this with PowerShell like this:
Import-Module MicrosoftPowerBIMgmt
Import-Module MicrosoftPowerBIMgmt.Profile
$password = "xxxxx" | ConvertTo-SecureString -asPlainText -Force
$username = "xxxxx#yyyyy.com"
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
Connect-PowerBIServiceAccount -Credential $credential
Invoke-PowerBIRestMethod -Url 'groups/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/datasets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/UpdateParameters' -Method Post -Body '{
"updateDetails": [
{
"name": "ServerName",
"newValue": "SQLSERVER2019"
},
{
"name": "DatabaseName",
"newValue": "AdventureWorks2019"
}
]
}'
Invoke-PowerBIRestMethod -Url 'groups/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/datasets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/refreshes' -Method Post
Disconnect-PowerBIServiceAccount
If you can't use parameters, e.g. Live connection to SSAS, the connection string could be changed using Update Datasources In Group REST API call. In PowerShell this could be done like this:
Import-Module MicrosoftPowerBIMgmt
Import-Module MicrosoftPowerBIMgmt.Profile
$password = "xxxxx" | ConvertTo-SecureString -asPlainText -Force
$username = "xxxxx#yyyyy.com"
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
Connect-PowerBIServiceAccount -Credential $credential
Invoke-PowerBIRestMethod -Url 'groups/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/datasets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Default.UpdateDatasources' -Method Post -Body '{
"updateDetails": [
{
"datasourceSelector": {
"datasourceType": "AnalysisServices",
"connectionDetails": {
"server": "My-As-Server",
"database": "My-As-Database"
}
},
"connectionDetails": {
"server": "New-As-Server",
"database": "New-As-Database"
}
}
]
}'
Disconnect-PowerBIServiceAccount
Note, that you need to provide both old and new server and database names.
In C# you can do the same in a very similar way, even without Power BI Client:
var group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var dataset_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var restUrlUpdateParameters = $"https://api.powerbi.com/v1.0/myorg/groups/{group_id}/datasets/{dataset_id}/Default.UpdateParameters";
var postData = new { updateDetails = new[] { new { name = "ServerName", newValue = "NEWSERVER" }, new { name = "DatabaseName", newValue = "Another_AdventureWorks2016" } } };
var responseUpdate = client.PostAsync(restUrlUpdateParameters, new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json")).Result;
var restUrlRefreshDataset = $"https://api.powerbi.com/v1.0/myorg/groups/{group_id}/datasets/{dataset_id}/refreshes";
var responseRefresh = client.PostAsync(restUrlRefreshDataset, null).Result;
Using the Power BI C# client can make you life easier, e.g. refreshing the report can be made this way:
var group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var dataset_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var credentials = new TokenCredentials(accessToken, "Bearer");
using (var client = new PowerBIClient(new Uri("https://api.powerbi.com"), credentials))
{
client.Datasets.RefreshDatasetInGroup(group_id, dataset_id);
}
When calling the API, you need to provide an access token. To acquire it use ADAL or MSAL libraries, e.g. with code like this:
private static string resourceUri = "https://analysis.windows.net/powerbi/api";
private static string authorityUri = "https://login.windows.net/common/"; // It was https://login.windows.net/common/oauth2/authorize in prior versions
private static string clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // Register at https://dev.powerbi.com/apps
private static string groupId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
private static string reportId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
private static AuthenticationContext authContext = new AuthenticationContext(authorityUri, new TokenCache());
public string Authenticate()
{
AuthenticationResult authenticationResult = null;
// First check is there token in the cache
try
{
authenticationResult = authContext.AcquireTokenSilentAsync(resourceUri, clientId).Result;
}
catch (AggregateException ex)
{
AdalException ex2 = ex.InnerException as AdalException;
if ((ex2 == null) || (ex2 != null && ex2.ErrorCode != "failed_to_acquire_token_silently"))
{
MessageBox.Show(ex.Message);
return;
}
}
if (authenticationResult == null)
{
var uc = new UserPasswordCredential("user#example.com, "Strong password");
try
{
authenticationResult = authContext.AcquireTokenAsync(resourceUri, clientId, uc).Result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ex.InnerException == null ? "" : Environment.NewLine + ex.InnerException.Message);
return;
}
}
if (authenticationResult == null)
MessageBox.Show("Call failed.");
else
{
return authenticationResult.AccessToken;
}
}

How to delete SharePoint Site Collection using pnp csom programmatically

I have a requirement on SharePoint Online Office 365. As per my requirement, I have to delete all the Site Collection from SharePoint Online Office 365 Admin Center using pnp csom programmatically.
Anyone can suggest that how can I delete all the Site Collection?
You can use DeleteSiteCollection extension method to remove the site collection.
It can be used as below:
string userName = "admin#tenant.onmicrosoft.com";
string password = "password";
string siteUrl = "https://tenant-admin.sharepoint.com/";
using (ClientContext clientContext = new ClientContext(siteUrl))
{
SecureString securePassword = new SecureString();
foreach (char c in password.ToCharArray())
{
securePassword.AppendChar(c);
}
clientContext.AuthenticationMode = ClientAuthenticationMode.Default;
clientContext.Credentials = new SharePointOnlineCredentials(userName, securePassword);
var tenant = new Tenant(clientContext);
// use false, if you want to keep site collection in recycle bin
tenant.DeleteSiteCollection("https://tenant.sharepoint.com/sites/Test", true);
}
Connect to SharePoint Online with global administrator account:
Connect-PnPOnline https://tenant-admin.sharepoint.com
Remove specific site collection by url:
Remove-PnPTenantSite -Url https://tenant.sharepoint.com/sites/sitename-Force -SkipRecycleBin
Remove-PnPTenantSite

Get user details from SharePoint with PowerShell

I'm using this PowerShell script to get site owners:
$siteUrl = Read-Host "enter site url here:"
$rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl)
$spWebApp = $rootSite.WebApplication
foreach($site in $spWebApp.Sites)
{
foreach($siteAdmin in $site.RootWeb.SiteAdministrators)
{
Write-Host "$($siteAdmin.ParentWeb.Url) - $($siteAdmin.DisplayName)"
}
$site.Dispose()
}
$rootSite.Dispose()
I want that it will print some details of the site owner like phone number and email. How can I achieve that?
You have two choices I think. Access the SPUser properties or get information from active directory.
In the first case, are you not able to access the properties as you did for DisplayName? I mean if you have a SPUser object to get the email just use:
write-output "$($siteAdmin.Email)"
For information about to get the user properties from active directory, you can easily implement the solution provided in the following question. It worked fine for me.
Hope this helps
EDIT with improvement
Standing from MS Documentation you have some properties avaialble, see SPUSer Members. FOr example you have not phone.
To get something from the active directory try to change the following function so that it returns the attributes you need (tested on windows 2k8 server):
function Get-AD-Data {
$strFilter = "(&(objectCategory=User))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$objSearcher.FindAll() | select #{L="User";E={$_.properties.displayname}},
#{L="Department";E={$_.properties.department}},
#{L="MemberOf";E={$_.properties.memberof}}
}
This function returns all users from AD along with the selected attributes. To get information from a specific user you would use (I guess):
$ad_userdetails = Get-AD-Data | ? {$_.user -eq $siteAdmin.Name}
Cheers

Resources