title: Power Bi Publish Report with Service Principal with c# under .NET 4.8
body: <p>i have some issues publishing a Report using a service Principal with c# under .NET 4.8</p>
  <p>I can get a bearer token in a given TenantID, with this token the service principal is able to fetch the groups from Power BI Cloud and also downloads Reports from the GroupID readed before.
  But if i try to upload a report with this token, the service principal will not be able to doning that. The result is 403, forbidden</p>
  <p>If i use a real exisiting user get the Bearer Token bei using Username and Password, i'm also be able to fetch the groups from Power Bi Cloud, downloads Report from the Group and also upload a Report to the Group. The Token i generate without TenantID by using common. The result is 202, accepted.</p>
  <p>The Service Principal has Tenant.Read and Tenant.Readwrite permission.</p>
  <p>The upload of the Report by using the service Principal works fine if i try it in Powershell, so i would thing that the permissions are correct.</p>
  <p>Has someone an idea, why the same code is not willing to work with Service Principal, but with user?</p>
  <p>My code for getting the bearer Token looks (after a lot of tries) like so:</p>
  <pre><code>   public string GetAccessToken(string resourceUri= &quot;https://analysis.windows.net/powerbi/api&quot;, 
          string grantType= &quot;password&quot;, string scope= &quot;openid&quot;, string loginAddress= &quot;https://login.microsoftonline.com/common/oauth2/token&quot;)
           
          {
              try { 
              log.Info(&quot;Get Bearer Token&quot;);
              StringBuilder body = new StringBuilder();
              body.Append(&quot;resource=&quot; + HttpUtility.UrlEncode(resourceUri));
              body.Append(&quot;&amp;client_id=&quot; + HttpUtility.UrlEncode(conf.ClientID));
              body.Append(&quot;&amp;grant_type=&quot; + HttpUtility.UrlEncode(grantType));
              if (grantType == &quot;password&quot;)
              {
                  body.Append(&quot;&amp;username=&quot; + HttpUtility.UrlEncode(conf.User));
                  body.Append(&quot;&amp;password=&quot; + HttpUtility.UrlEncode(conf.Password));
               }
                  body.Append(&quot;&amp;scope=&quot; + HttpUtility.UrlEncode(scope));
                  body.Append(&quot;&amp;client_secret=&quot; + HttpUtility.UrlEncode(conf.ClientSecret));
  
                  using (WebClient web = new WebClient())
                  {
                      try
                      {
                          web.Headers.Add(&quot;client-request-id&quot;, Guid.NewGuid().ToString());
                          web.Headers.Add(&quot;return-client-request-id&quot;, &quot;true&quot;);
                          string data = web.UploadString(loginAddress, body.ToString());
                          dynamic result = JsonConvert.DeserializeObject(data);
                          log.Info(&quot;Found Bearer Token&quot;);
                          c_BearerTok= result.access_token;
                          return result.access_token;
                      }
                      catch (Exception e)
                      {
                          log.Error(&quot;Geting Bearer Token went wrong&quot;);
                          return null;
                      }
                  }
              }
              catch { return null; }
          }
  </code></pre>
  <p>This will be the Call with service principal:</p>
  <pre><code>GetAccessToken(grantType: &quot;client_credentials&quot;, loginAddress: $&quot;https://login.microsoftonline.com/{Conf.TenantID}/oauth2/token&quot;);
  </code></pre>
  <p>And this will be the Call with User:</p>
  <pre><code>GetAccessToken();
  </code></pre>
  <p>The Code for uploading the Report looks like:</p>
  <pre><code> private void  PerformFileUploadAsync(string url, string bear = &quot;&quot;)
          {
              try
              {
                  if (bear == &quot;&quot;)
                  {
                      bear = c_BearerTok;
                  }
                  log.Info(&quot;Perform File Upload, Read File: &quot;+ PBIXUploadDir);
                  var dummy = File.Open(PBIXUploadDir, FileMode.Open);
                  var pbixBodyContent = new StreamContent(dummy);
                  // add headers for request body content
                  pbixBodyContent.Headers.Add(&quot;Content-Type&quot;, &quot;application/octet-stream&quot;);
                  pbixBodyContent.Headers.Add(&quot;Content-Disposition&quot;,
                                               @&quot;form-data; name=&quot;&quot;file&quot;&quot;; filename=&quot;&quot;&quot; + PBIXUploadDir + @&quot;&quot;&quot;&quot;);
                  // load PBIX content into body using multi-part form data
                  MultipartFormDataContent requestBody = new MultipartFormDataContent(Guid.NewGuid().ToString());
                  requestBody.Add(pbixBodyContent);
  
                  HttpClient client = new HttpClient();
                  client.DefaultRequestHeaders.Add(&quot;Accept&quot;, &quot;application/json&quot;);
                  client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, &quot;Bearer &quot; + bear);
                  // post request
                  log.Info(&quot;Begin upload process&quot;);
                  var response = client.PostAsync(url, requestBody).Result;
                  // check for success
                  log.Info(&quot;response:&quot; + response.StatusCode.ToString());
                  dummy.Close();
                  pbixBodyContent = null;
                  if (response.StatusCode.ToString().Equals(&quot;Accepted&quot;))
                  {
                      Console.WriteLine(&quot;Import process complete: &quot; + response.Content.ReadAsStringAsync().Result);
                  }
              } catch (Exception e)
              {
                  log.Error(&quot;Error uploadprocess&quot;);
              }
          }
  </code></pre>
  <p>The url for the procedure:</p>
  <pre><code>string.Concat(&quot;https://api.powerbi.com/beta/myorg/groups/&quot;, groupId, &quot;/imports/?datasetDisplayName=&quot;, displayName);
  </code></pre>
  <p>And to complete the code, here is the function that maks the call</p>
  <pre><code> public int UploadImportAsync(string displayName=&quot;&quot;, string groupId = null, string bear = &quot;&quot;)
          {
              try
              {
                  if (bear == &quot;&quot;)
                  {
                      bear = c_BearerTok;
                  }
                  if ( displayName == &quot;&quot;){
                      displayName = conf.PBIXCompressFileName;
                  }
                  if(groupId ==null)
                  {
                      groupId = GetGroups()[conf.WorkSpacename]; 
                      
                  }
                  log.Info(&quot;Upload File: &quot; + displayName);
  
                  if (string.IsNullOrEmpty(displayName))
                      throw new ArgumentNullException(nameof(displayName), &quot;Display name cannot be null&quot;);
                  string url = &quot;https://api.powerbi.com/beta/myorg/imports/?dataSetDisplayName=&quot; + HttpUtility.UrlEncode(displayName);
  
                  if (groupId != &quot;&quot;)
                  {
                      url = string.Concat(&quot;https://api.powerbi.com/beta/myorg/groups/&quot;, groupId, &quot;/imports/?datasetDisplayName=&quot;, displayName);
                  }
  
                  if (conf.ConflictInstruction != &quot;&quot;)
                  {
                      url += &quot;&amp;nameConflict=&quot; + conf.ConflictInstruction;
                  }
                  log.Info(&quot;Upload Url &quot; + url,logDeep: FileLog.logDeep.StepLog);
                  PerformFileUploadAsync(url);
                  return 0;
              }
              catch
              {
                  log.Error(&quot;Cant upload&quot;);
                  return -1;
              }
          }
  
  </code></pre>
  <p>this is the way i have tried the upload with service principal over powershell (works):</p>
  <pre><code>Connect-PowerBIServiceAccount -Tenant [TenantId] -ServicePrincipal -Credential (Get-Credential)
  Get-PowerBIWorkspace
  New-PowerBIReport -Path .\newReport.pbix -Name 'New Report' -WorkspaceId f95755a1-950c-46bd-a912-xxx
  </code></pre>
  <p>I don't wont to use powershell for this single step in a large process. And also if i go this way, we will add the powershell extension for PowerBI on every environment that we are using (actualy 5, but more in the future), the .Net code i can complete encapsulate in our application, without doing  extra steps on every environment. We can use the userprofile, but on the long term it makes maintenancework, so expiered passwords, Multifactorauth. etc. So i need a way to do this with the Serviceprincipal, but after a lot of tries i don't have some ideas anymore. So, in hope to finde someone that has done this before i wrote my question here.</p>