how to authenticate documentdb stored procedure REST call - c#

EDIT: Problem was parameters.ToString(). Also like Lengning Liu pointed out you need to add '/sprocs/sprocname'
I'm trying to execute a stored procedure in Azure DocumentDB with a REST call in ASP.NET Core 1.0 / C#.
I use the method on this MSDN page to generate the hash:
https://msdn.microsoft.com/library/azure/dn783368.aspx
I'm getting a 401 Unauthorized repsonse. Is "sprocs" the right document type, or do i have to enter a different type?
public class Database
{
public async Task<HttpResponseMessage> StoredProcedure(string database, string collection, string storedProcedure, string[] parameters)
{
//https://{databaseaccount}.documents.azure.com/dbs/{db-id}/colls/{coll-id}/sprocs/{sproc-name}
string resourceLink = $"dbs/{database}/colls/{collection}";
var client = Client("POST", resourceLink, "sprocs");
StringContent content = new StringContent(parameters.ToString(), null, "application/query+json");
var uri = new Uri(_endpointUri, $"dbs/{database}/colls/{collection}/sprocs/{storedProcedure}");
HttpResponseMessage response = await client.PostAsync(uri, content);
return response;
}
private HttpClient Client(string verb, string resourceLink, string resourceType)
{
var client = new HttpClient();
var utc_date = DateTime.UtcNow.ToString("r");
client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", "2015-12-16");
client.DefaultRequestHeaders.Add("x-ms-max-item-count", "10000");
var authHeader = GenerateMasterKeyAuthorizationSignature(utc_date, verb, resourceLink, resourceType, _authorizationKey, "master", "1.0");
client.DefaultRequestHeaders.Add("authorization", authHeader);
return client;
}
private static string GenerateMasterKeyAuthorizationSignature(string utc_date, string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion)
{
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceId,
utc_date.ToLowerInvariant(),
""
);
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
return System.Net.WebUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
}
}

it seems that when you generated the auth token, in the resourceId part, you left out "/sprocs/{storedProcedure}". You have included it in the uri, which is correct.
I am attaching a sample powershell script that, hopefully, will also help you to see how auth token is generated.
Add-Type -AssemblyName System.Web
$accountName = "<db account name>"
$connectionKey = "<secret key>"
$collectionName = "<coll name>"
$databaseName = "<db name>"
Write-host ("Account " + $accountName)
Write-host ("Database " + $databaseName)
Write-host ("Collection " + $collectionName)
function GetKey([System.String]$Verb = '',[System.String]$ResourceId = '',
[System.String]$ResourceType = '',[System.String]$Date = '',[System.String]$masterKey = '') {
$keyBytes = [System.Convert]::FromBase64String($masterKey)
$text = #($Verb.ToLowerInvariant() + "`n" + $ResourceType.ToLowerInvariant() + "`n" + $ResourceId + "`n" + $Date.ToLowerInvariant() + "`n" + "" + "`n")
$body =[Text.Encoding]::UTF8.GetBytes($text)
$hmacsha = new-object -TypeName System.Security.Cryptography.HMACSHA256 -ArgumentList (,$keyBytes)
$hash = $hmacsha.ComputeHash($body)
$signature = [System.Convert]::ToBase64String($hash)
Write-Host($text)
[System.Web.HttpUtility]::UrlEncode($('type=master&ver=1.0&sig=' + $signature))
}
function GetUTDate() {
$date = get-date
$date = $date.ToUniversalTime();
return $date.ToString("r", [System.Globalization.CultureInfo]::InvariantCulture);
}
function GetDatabases() {
$uri = $rootUri + "/dbs"
$hdr = BuildHeaders -resType dbs
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $hdr
$response.Databases
Write-Host ("Found " + $Response.Databases.Count + " Database(s)")
}
function GetCollections([string]$dbname){
$uri = $rootUri + "/" + $dbname + "/colls"
$headers = BuildHeaders -resType colls -resourceId $dbname
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
$response.DocumentCollections
Write-Host ("Found " + $Response.DocumentCollections.Count + " DocumentCollection(s)")
}
function BuildHeaders([string]$action = "get",[string]$resType, [string]$resourceId){
$authz = GetKey -Verb $action -ResourceType $resType -ResourceId $resourceId -Date $apiDate -masterKey $connectionKey
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", $authz)
$headers.Add("x-ms-version", '2015-12-16')
$headers.Add("x-ms-date", $apiDate)
$headers
}
function PostDocument([string]$document, [string]$dbname, [string]$collection){
$collName = "dbs/"+$dbname+"/colls/" + $collection
$headers = BuildHeaders -action Post -resType docs -resourceId $collName
$headers.Add("x-ms-documentdb-is-upsert", "true")
$uri = $rootUri + "/" + $collName + "/docs"
Write-host ("Calling " + $uri)
$response = Invoke-RestMethod $uri -Method Post -Body $json -ContentType 'application/json' -Headers $headers
$response
}
function PostSprocQuery([string]$dbname, [string]$collection){
$sprocName = "dbs/"+$dbname+"/colls/" + $collection + "/sprocs/samplesproc"
$headers = BuildHeaders -action Post -resType sprocs -resourceId $sprocName
$uri = $rootUri + "/" + $sprocName
Write-host ("Calling " + $uri)
write-host $authz
write-host $apiDate
$response = Invoke-RestMethod $uri -Method Post -Body $json -ContentType 'application/json' -Headers $headers
$response
}
$rootUri = "https://" + $accountName + ".documents.azure.com"
write-host ("Root URI is " + $rootUri)
#validate arguments
$apiDate = GetUTDate
$db = GetDatabases | where { $_.id -eq $databaseName }
if ($db -eq $null) {
write-error "Could not find database in account"
return
}
$dbname = "dbs/" + $databaseName
$collection = GetCollections -dbname $dbname | where { $_.id -eq $collectionName }
if($collection -eq $null){
write-error "Could not find collection in database"
return
}
$json = #"
{
"id": "3"
}
"#
PostDocument -document $json -dbname $databaseName -collection $collectionName
$json = #"
[
"samplesproc"
]
"#
PostSprocQuery -document $json -dbname $databaseName -collection $collectionName

Related

How to supply parameters to powershell script (System.Management.Automation.Powershell)?

I have a need to run PowerShell scripts from C# application. I like using method AddScript for this. And it works quite nice. However, it does not seem to work as expected when I added parameters to script with method AddParameters.
Here is test payload (PowerShell):
param ([string]$Arg1, [string]$Arg2, [switch]$ArgParamless)
$filename = "payload_with_params.txt"
$filepath = $env:temp
$fullpath = Join-Path -Path $filepath -ChildPath $filename
$dt = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
$val = $dt+' '+$Arg1+' '+$Arg2+' '+$ArgParamless
Add-Content -Path $fullpath -Value "$val"
It works just fine if I push it from PS like this:
.\payload_with_params.ps1 -Arg1 "Bla 1" -Arg2 "Bla 2" -ArgParamless
Result:
2023.01.19 16:58:10 Bla 1 Bla 2 True
The C# code (oversimplified):
string command = File.ReadAllText(pathToPS1);
List<CommandParameter> paramList = new List<CommandParameter>();
paramList.Add(new CommandParameter("Arg1", "Bla 1"));
paramList.Add(new CommandParameter("Arg2", "Bla 2"));
paramList.Add(new CommandParameter("ArgParamless"));
using (PowerShell ps = PowerShell.Create())
{
//Adding script file content to object
ps.AddScript(command);
if (paramList != null)
{
if (paramList.Count > 0)
{
//Adding Params to object;
ps.AddParameters(paramList);
}
}
//Launching
ps.Invoke();
}
And the result:
2023.01.19 16:54:00 System.Management.Automation.Runspaces.CommandParameter System.Management.Automation.Runspaces.CommandParameter False
So.. it's not working as I expected. How should I supply parameters to script?
For [switch] parameters, you'll want to bind a bool - PowerShell will interpret true as "Present" and false as "Absent":
paramList.Add(new CommandParameter("ArgParamless", true));
Well, it turns out AddParameter(CommandParameter) approach doesn't work.
This approach is working:
string command = File.ReadAllText(pathToPS1);
using (PowerShell ps = PowerShell.Create())
{
//Adding script file content to object
ps.AddScript(command);
if (paramList != null)
{
if (paramList.Count > 0)
{
//Adding Params to object;
ps.AddArgument("-Arg1 XXXXXX");
}
}
//Launching
ps.Invoke();
}
On PowerShell end this arguments can be extracted for usage from $args array:
$arglist = $args -join " "
Even better approach using hashtable:
string command = File.ReadAllText(pathToPS1);
using (PowerShell ps = PowerShell.Create())
{
//Adding script file content to object
ps.AddScript(command);
if (paramList != null)
{
if (paramList.Count > 0)
{
//Adding Params to object;
var hashtable = new Hashtable {
{ "Arg1", "XXXXXX" },
{ "Arg2", "YYYYYY" },
{ "ArgParamless", true }
};
ps.AddArgument(hashtable);
}
}
//Launching
ps.Invoke();
}
And that's what you do on PS side to use it:
function Wrapper-Test ([string]$Arg1, [string]$Arg2, [switch]$ArgParamless) {
$Result = $Args1+' '+$Args2+' '+$ArgParamless
return $Result
}
$MyArgs = "";
$MyArg = $args[0]
$MyArgs = Wrapper-Test #MyArg;
$filename = "payload_with_params.txt"
$filepath = $env:temp
$fullpath = Join-Path -Path $filepath -ChildPath $filename
$dt = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
$arglist = $args -join " "
$val = $dt + " " + $MyArgs
Add-Content -Path $fullpath -Value "$val"

WPF UI passing output from powershell function into textbox

I am creating a WPF UI and I am having trouble getting the output from a foreach loop within a function into a WPF textbox. The variables I need to output ar $id $newValue and $serviceName
#Iteration through services perform string replacement
ForEach ($Response in $Responses ) {
$id = $Response.id
$newValue = $Response.value.Replace("\Service", "")
$serviceName = $Response.serviceName
Write-Output $id
Write-Output $serviceName
Write-Output $newValue
#if statement here for testing this should be removed
if ($id -eq ) {
$Payload = #{
hostName = '';
hostOperatingSystem = 0;
optionalFilter1 = '';
optionalFilter2 = '';
description = '';
serviceName = $serviceName;
sectionName = "Services";
signature = 'null';``
value = $newValue
} | ConvertTo-Json -Depth 10
#Service Configuration API PUT method call to update fullpath value
$newURI = $URI + '/' + $id
$Response = Invoke-RestMethod -Uri $newURI -Method PUT -Headers $Headers -Body $Payload -ContentType 'application/json'
}
#here for testing this should be removed
else {
$output.text = "Nothing to do"
}
}
$output.text = "Done!"
}
[xml]$Form = Get-Content "MainWindow.xaml"
$NR = (New-Object System.Xml.XmlNodeReader $Form)
$Win = [Windows.Markup.XamlReader]::Load($NR)
$URL = $win.FindName("URL")
$cid = $win.FindName("Client_ID")
$csecret = $win.FindName("Client_Secret")
$update = $Win.FindName("Update")
$output = $Win.FindName("output")
$update.Add_Click( {
$UserAuthRoute = $URL.text
$client_id = $cid.text
$client_secret = $csecret.text
if ($UserAuthRoute -eq "" -or $client_secret -eq "" -or $client_secret -eq "") {
[System.Windows.MessageBox]::Show("Please enter in all values","Variables Required")
}else{
UpdateServices $UserAuthRoute $client_id $client_secret
$output.text += "Service ID:"+$id , "Service Name: "+$serviceName, "$newValue"
}
})
$Win.ShowDialog() ```
The solution was to append the value and separate with a newline
$output.text += "Service ID: " + $id + "`r`n" + "Service Name: " + $serviceName + "`r`n" + "Service Value: " + $newValue + "`r`n"

Invoke-Command with credentials

I am doing an Invoke Command method using powershell and I need to know how to input credentials into the script. For example at the moment I have:
Invoke-Command -ComputerName 0.0.0.0 -ScriptBlock { Get-Command }
But I need to add Credentials all in one. I need it in a way it wont ask me to type in credentials, it just takes them from the script. Looking for something like this:
Invoke-Command -ComputerName 0.0.0.0 -ScriptBlock { Get-Command } -Credential username ,password
This is my context:
private void button1_Click(object sender, EventArgs e) {
try {
richTextBox1.Clear();
if (RunRemotely == true) {
richTextBox1.Text = RunScript("Invoke-Command -ComputerName" + richTextBox3.Text + " -ScriptBlock { " + richTextBox2.Text + "} -Credential $cred");
} else {
richTextBox1.Text = RunScript(richTextBox2.Text);
}
} catch (Exception error) {
richTextBox1.Text += String.Format("\r\nError in script : {0}\r\n", error.Message);
}
}
I have tried:
private void button1_Click(object sender, EventArgs e) {
try {
richTextBox1.Clear();
if (RunRemotely == true) {
$username = 'foo'
$password = 'bar'
$secpw = ConvertTo - SecureString $password - AsPlainText - Force
$cred = New - Object** Management.Automation.PSCredential($username, $secpw)
richTextBox1.Text = RunScript("Invoke-Command -ComputerName" + richTextBox3.Text + " -ScriptBlock { " + richTextBox2.Text + "} -Credential $cred");
} else {
richTextBox1.Text = RunScript(richTextBox2.Text);
}
} catch (Exception error) {
richTextBox1.Text += String.Format("\r\nError in script : {0}\r\n", error.Message);
}
}
While doing this:
$username = 'foo'
$password = 'bar'
$secpw = ConvertTo - SecureString $password - AsPlainText - Force
$cred = New - Object Management.Automation.PSCredential($username, $secpw)
It says the name 'username' does not exist in the current context.
Build a PSCredential object from username and password and pass that to the -Credential parameter.
$username = 'foo'
$password = 'bar'
$secpw = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object Management.Automation.PSCredential ($username, $secpw)
Invoke-Command ... -Credential $cred

DotCMIS connect to SharePoint Foundation 2013

I would like to connect DotCMIS.dll to my SharePoint but does not work correct.
I open the script in the SharePoint 2013 Management Shell.
I use my user permissions (This is not a Farm user)
Probably here's the problem with giving the correct link. org.apache.chemistry.dotcmis.binding.atompub.url=?
Have you got any idea where link in sharepoint have to go?
Website of example:
http://chemistry.apache.org/dotnet/powershell-example.html
Error
You cannot call a method on a null-valued expression.
At line:6 char:7
+ $b = $contentStream.Stream.Read($buffer, 0, 4096)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Important part of my Script
$sp["org.apache.chemistry.dotcmis.binding.atompub.url"] = "http://localhost/_layouts/15/start.aspx#/SitePages/WebSite.aspx"
$sp["org.apache.chemistry.dotcmis.user"] = "mylogin"
$sp["org.apache.chemistry.dotcmis.password"] = "mypassword"
All Script
# load DotCMIS DLL
[Reflection.Assembly]::LoadFile("C:\dotCmisServer\DotCMIS.dll")
# -----------------------------------------------------------------
# helper functions
function New-GenericDictionary([type] $keyType, [type] $valueType) {
$base = [System.Collections.Generic.Dictionary``2]
$ct = $base.MakeGenericType(($keyType, $valueType))
New-Object $ct
}
function New-ContentStream([string] $file, [string] $mimetype) {
$fileinfo = ([System.IO.FileInfo]$file)
$contentStream = New-Object "DotCMIS.Data.Impl.ContentStream"
$contentStream.Filename = $fileinfo.Name
$contentStream.Length = $fileinfo.Length
$contentStream.MimeType = $mimetype
$contentStream.Stream = $fileinfo.OpenRead()
$contentStream
}
function Download-ContentStream([DotCMIS.Client.IDocument] $document, [string] $file) {
$contentStream = $document.GetContentStream()
$fileStream = [System.IO.File]::OpenWrite($file)
$buffer = New-Object byte[] 4096
do {
$b = $contentStream.Stream.Read($buffer, 0, 4096)
$fileStream.Write($buffer, 0, $b)
}
while ($b -ne 0)
$fileStream.Close()
$contentStream.Stream.Close()
}
# -----------------------------------------------------------------
# create session
$sp = New-GenericDictionary string string
$sp["org.apache.chemistry.dotcmis.binding.spi.type"] = "atompub"
$sp["org.apache.chemistry.dotcmis.binding.atompub.url"] = "http://localhost/_layouts/15/start.aspx#/SitePages/WebSite.aspx"
$sp["org.apache.chemistry.dotcmis.user"] = "mylogin"
$sp["org.apache.chemistry.dotcmis.password"] = "mypassword"
$factory = [DotCMIS.Client.Impl.SessionFactory]::NewInstance()
$session = $factory.GetRepositories($sp)[0].CreateSession()
# print the repository infos
$session.RepositoryInfo.Id
$session.RepositoryInfo.Name
$session.RepositoryInfo.Vendor
$session.RepositoryInfo.ProductName
$session.RepositoryInfo.ProductVersion
# get root folder
$root = $session.GetRootFolder()
# print root folder children
$children = $root.GetChildren()
foreach ($object in $children) {
$object.Name + " (" + $object.ObjectType.Id + ")"
}
# run a quick query
$queryresult = $session.Query("SELECT * FROM cmis:document", $false)
foreach ($object in $queryresult) {
foreach ($item in $object.Properties) {
$item.QueryName + ": " + $item.FirstValue
}
"----------------------------------"
}
# create a folder
$folderProperties = New-GenericDictionary string object
$folderProperties["cmis:name"] = "myNewFolder"
$folderProperties["cmis:objectTypeId"] = "cmis:folder"
$folder = $root.CreateFolder($folderProperties)
# create a document
$documentProperties = New-GenericDictionary string object
$documentProperties["cmis:name"] = "myNewDocument"
$documentProperties["cmis:objectTypeId"] = "cmis:document"
$source = $home + "\source.txt"
$mimetype = "text/plain"
$contentStream = New-ContentStream $source $mimetype
$doc = $folder.CreateDocument($documentProperties, $contentStream, $null)
# download a document
$target = $home + "\target.txt"
Download-ContentStream $doc $target
# clean up
$doc.Delete($true)
$folder.Delete($true)
Unfortunately in SharePoint Foundation 2013 I have to write own C# software.
SharePoint Foundation 2013 IMPORT\EXPORT dbo.allDocs file\files

Powershell nothing returned from C#, same code runs in ps prompt with \r\n's removed

string cmd = " $srv = new-object Microsoft.SqlServer.Management.Smo.Server('" + svr + "')" + Environment.NewLine;
cmd += " $srv.Logins | where-object {$_.Name -eq 'DOMAIN\server55' } | select-object 'State'" + Environment.NewLine;
That code is added between the PSSnapin & PSSession code then invoked:
util>string prep = "$hasSnapin = get-pssnapin | Select { $_.Name.toLower().Trim() = 'sqlservercmdletsnapin100' }" + Environment.NewLine;
util>prep += "if ($hasSnapin -eq $null) { Add-Pssnapin SqlServerCmdletSnapin100 }" + Environment.NewLine;
util>cmd = prep;
util>cmd = "$pssessSql = New-PSSession -ComputerName " + svr + Environment.NewLine;
util>cmd += " Invoke-Command -session $pssessSql -ScriptBlock {" + Environment.NewLine;
util>cmd += " " + " sqlps -nologo -noprofile -command {" + Environment.NewLine;
util>cmd += " " + command + " }" + Environment.NewLine;
util>cmd += " }" + Environment.NewLine;
util>cmd += " Remove-PSSession -Session $pssessSql" + Environment.NewLine;
util>cmd += " exit";
util>try {
util>IList<System.Management.Automation.PSObject> results = pipeline.Invoke();
util>runspace.Close();
util>return results;
util>}
If I capture the script going to the Invoke it works by replacing "\r\n" with a newline, why wouldn't it work in C#, I have a other scripts working from the C# so may be missing something obvious, here's the captured code that runs from a ps prompt:
$hasSnapin = get-pssnapin | Select { $_.Name.toLower().Trim() = 'sqlservercmdletsnapin100' }
if ($hasSnapin -eq $null) { Add-Pssnapin SqlServerCmdletSnapin100 }
$pssessSql = New-PSSession -ComputerName Server54
Invoke-Command -session $pssessSql -ScriptBlock {
sqlps -nologo -noprofile -command {
$srv = new-object Microsoft.SqlServer.Management.Smo.Server('Server54')
$srv.Logins | where-object {$_.Name -like 'DOMAIN\Server55$' } | select-object 'State' }
}
Remove-PSSession -Session $pssessSql
exit
Thanks for any clues, I've had trouble with nested quotes but able to get most of those so this is I think from the \r\n's in the code but not sure how to find that out, I can't get the results to return from the app but the whole script does fine via a ps prompt.
Could the problem be the loading of the snapin on the remote end?
You have:
cmd = prep;
cmd = "$pssessSql ...";
which means that the prep code is completely ignored/overwritten by the second assignment.
One alternative is is to unconditionally add the snapin, specifying that you want errors to be ignored.
Add-Pssnapin -ErrorAction SilentlyContinue SqlServerCmdletSnapin100
Found it, the code is fine but I was using formatting, when I commented that out I was able to get my results. Before that it was returning formatting objects but I couldn't get values from them ... glad I tried without:
query += "\r\n" + "$data = $cmdCheckLogin.ExecuteReader()";
query += "\r\n" + "$dt = new-object System.Data.DataTable";
query += "\r\n" + "$dt.Load($data)";
query += "\r\n" + "$dt"; // | format-table -hidetableheaders";
query += "\r\n" + "$conn.Close()";

Categories

Resources