locked
How to pass a JSON object into the Invoke-WebRequest command in ASP.NET MVC using instance of PowerShell. RRS feed

  • Question

  • User-1549556126 posted

    I am creating an application that interacts with the Active Directory to manage members of the AD Group. I am trying to do that using the DLL called System.Management.Automation, initiating the instance of PowerShell and executing scripts to fill all the data into my web page. So far from the web page I am able to generate a JSON object in the format shown below. I need to pass this JSON object to execute the operation to remove members of the AD Group whose GUID is already there in the JSON Object. How do I pass this JSON object to the Inovke-WebRequest command and execute the changes in active directory? 

    MyObject= {     "Directory": myDomain.com,
                    "Group": GroupGUID.value, // a3d65150-1739-4cc7-80d2-7b97c76b0aff
                    "Remove": RemoveMembers, // JSON Array of Members with AD Attribute ObjectGUID@myDomain.com 
    // Sample Value: "67530d5e-2e51-446f-bfa0-1bb6fd5dc444@myDomain.com"
    "RemoveAfter": RemoveAfter, // JSON Array of Members with Remove Date & AD Attribute ObjectGUID@myDomain.com
    //Sample Value: "2019-10-09|139d4454-b531-4d62-920d-1d0817732c83@myDomain.com" "ResultFormat": "json", "OnBehalfOf": "USER"
    };

    FYI, I have a function in C# that interacts with Powershell scripts.  

            private string PowerShellExecutorStr(string script, string arg)
            {
                string outString = "";
                var shell = PowerShell.Create();
                shell.Commands.AddCommand(script);
                shell.Commands.AddArgument(arg);
                var results = shell.Invoke();
                if (results.Count > 0)
                {
                    var builder = new StringBuilder();
                    foreach (var psObj in results)
                    {
                        builder.Append(psObj.BaseObject.ToString() + "\r\n");
                    }
                    outString = Server.HtmlEncode(builder.ToString());
                }
                shell.Dispose();
                return outString;
            }
    

    If I just create the script for Invoke-WebRequest and pass the path of the script into the this function and load the json object & URI as an argument will it work? Below is my full code in jQuery that creates the object on button click I am just thinking to create a script that has Invoke-WebRequest command and pass the json object & the current URI as an argument.

    //Make Array Object to pass in the API For Membership Update
            $("#btnUpdate").click(function () {
                var RemoveMembers = [];
                var RemoveAfter = [];
                var MemberUpdate = {};
                var GroupGUID = "";
                $("table [id*=ddlReqdAdjustment]").each(function () {
                    if ($(this).val() != "Keep") {
                        GroupGUID = $(this).parent().parent().children().eq(4)[0].innerText;
                        var date = $(this).parent().parent().children().eq(8)[0].firstElementChild.value;
                        var ObjectGUID = $(this).parent().parent().children().eq(3)[0].innerText + "@@" + $('#ddlDirectory').val();
                        var format = date + "|" + ObjectGUID;
                        if ($(this).val() == "Remove") {
                            var format = ObjectGUID;
                            RemoveMembers.push(format);
                        } else {
                            var format = date + "|" + ObjectGUID;
                            RemoveAfter.push(format);
                        }
                    }
                });
                MemberUpdate = {
                    "Directory": $('#ddlDirectory').val(),
                    "Group": GroupGUID,
                    "Remove": RemoveMembers,
                    "RemoveAfter": RemoveAfter,
                    "ResultFormat": "json",
                    "OnBehalfOf": "11051112"
                };
                console.log(MemberUpdate);
                return false;
            });

    Will that work with just one button click? The columns of ObjectGUID is hidden in my table. The screenshot of the module is here.

    Monday, July 1, 2019 6:15 PM

All replies

  • User753101303 posted

    Hi,

    Your design is unclear. On the server side you can do quite few things using https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.accountmanagement?view=netframework-4.8 rather than PowerShell.

    On the client side you can't use PowerShell from JavaScript to send http requests to the web server. Instead see https://learn.jquery.com/ajax/ to learn sending Ajax request from JavaScript using jQuery as well.

    Edit: or more accurately the browser is a "sandbox" (for security reasons) and you can only use "built-in" browser capabilities (jQuery is using that behind the scene) which includes for example not being able to run any directly any external application (including PowerShell scripts etc...)or uploading/downloading files without user consent etc...

    Monday, July 1, 2019 7:04 PM
  • User-1549556126 posted

    Hello PatriceSc,

    Thanks for the response.

    Yes my C# function is on the server side in the controller file. I have used .ajax to call that function and fill the data. I am thinking about using similar way to post the data. And pass the JSON object into the shell script. I am learning to use directory services recently & as this application is already built its hard for me to transition everything using PowerShell to directory services.

    Thanks,

    Monday, July 1, 2019 7:21 PM
  • User753101303 posted

    If using jQuery Ajax to read data and show them in your HTML page, you'll use jQuery Ajax as well to POST data from your HTML page to the controller which will then call PowerShell scripts on the same server to update AD.

    For now I really don't see where Invoke-WebRequest fits into this design?

    Monday, July 1, 2019 7:54 PM
  • User-1549556126 posted

    I am developing the script file (.ps1) which will have some arguments needed. I am passing the path of that script into the function above through HTML code. I am yet to create a logic however for reading the data Here's my Ajax Function:

    $.ajax({
                    type: "POST",
                    url: "/Group/FillSDC",
                    data: { GroupName: $("#ddlGroup option:selected").text().trim() },
                    success: function (response) {
                        $("#txtValues").val(response.message);
    
           })

    And on my controller side I am invoking the PowerShellExecutor Function to pass the shell script.

    [HttpPost]
            public JsonResult FillSDC(string GroupName)
            {
                var SDC = PowerShellExecutorStr(AppDomain.CurrentDomain.BaseDirectory + "Shell\\Get-Values.ps1", GroupName);
                return Json(new { message = SDC }, JsonRequestBehavior.AllowGet);
            }

    Monday, July 1, 2019 8:02 PM
  • User753101303 posted

    Ok this is more or less what I expect and still don't get where you are (or plan ?) to use Invoke-WebRequest and why?

    Do you mean that rather than passing all the parameters you need to get-values.ps1 or to return back all the values you want to your controller you plan to use Invoke-WebRequest from within Get-Values.ps1 to get additional data or send additional return values to your MVC app ???

    If using it already, please show the relevant line of code.

    If not, please explain from which source you are trying to post data and to which destination you are trying to post them ????

    Edit: "reading data" that is ? Your PowerShell script returns already a JSON string ???? if "passing a JSON object into the Inwoke-WebRequest" is just a way you think you could solve your unkown problem, please explain first which problem you are trying to solve.

    Monday, July 1, 2019 8:23 PM
  • User-1549556126 posted

    The code I mentioned is the code that fetches the data from AD it has nothing to do with Invoke-WebRequest. This is all read operations to fill the data into the components of the application. The idea of Invoke-WebRequest is to put into a separate shellscript that I am yet to design I have the JSON object with me. The problem that I have is that for the powershellexecutor function that I mentioned earlier I am not sure about how do I pass that JSON object into that function. As I am currently working on creating the script which will have params, I plan to reutilize the function for interacting with AD. This is a sample script that I am working on with dummy data to execute in POWERSHELL window.

    $uri = "http://localhost:44300/Group/GroupReview#"
    $params = {
          Directory : "myDomain.com",
          Group : "a3d65150-1739-4cc7-80d2-7b97c76b0aff",
          OnBehalfOf: "11051112",
          Remove: [
             0: "8a3fab53-4c8b-483d-89f0-e26de236a627@myDomain.com",
          ],
          RemoveAfter: [ ],
          ResultFormat: "json"
    
       }
    
    Invoke-WebRequest -Uri $uri -Method Post -Body $params

    The $params I want to pass it through the code in my ajax where I created the JSON object. I apologize for confusion.

    Monday, July 1, 2019 8:42 PM
  • User753101303 posted

    Still unclear but let's forget about that for now. It seems actually rather a PowerShell question. From a quick search it seems you are looking for https://devblogs.microsoft.com/scripting/playing-with-json-and-powershell/ (you also have a ConvertTo-Json cmdlet).

    You could also have a look at https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-6 (in particular it will deserialize automatically the json response as an object).

    If you need further help on this particular point it seems a PowerShell forum could be better.

    For now my understanding is :
    - you want to be able to call controllers in your MVC API from PowerShell which I could understand (though I would likely just use AD cmdlets unless my MVC app really offers more than basic AD operations)
    - what I find still weird is that it seems you'll do that from a script called using PowerShellExecutor ie you'll do an http ,request to the app from itself ???
    - when doing AD operations I'm using rather https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.accountmanagement?view=netframework-4.8 and use PowerShell when I have no other easy option (such as configuring Skype if I remember)

    Edit: ah or you meant how to pass an object to your script. I would try https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.psobject?view=pscore-6.2.0 and the PSObject(object) constructor.

    Monday, July 1, 2019 10:46 PM
  • User753101303 posted

    I've done some more testing. For passing a C# object to PowerShell I tried:

    script.AddCommand(@".\Post.ps1").AddParameter("object", new { title = "foo", body = "bar", userId = 1 });

    On the PowerShell side, ConvertTo-Json seems fine:

    param($object)
    $uri="https://jsonplaceholder.typicode.com/posts"
    $header=@{"Content-type"="application/json;charset=UTF-8"}
    Invoke-RestMethod -Method "POST" -Uri $uri -Header $header -Body (ConvertTo-Json $object)
    #Invoke-WebRequest -Method "POST" -Uri $uri -Header $header -Body (ConvertTo-Json $object)

    Invoke-RestMethod even returns a json response as an object to the C# side.

    Still::
    - it the call is done between controllers inside the same MVC app, I would skip the http call
    - even if calling an external API, I would anyway use HttpClient from C# rather than going through PowerShell

    Tuesday, July 2, 2019 7:27 AM
  • User-1549556126 posted

    Hello PatriceSc,

    So as per your response yes using HTTP client is a better choice however, I am limited in knowledge to directory services class library in C# used to manage AD, so as a temporary fix I have created this solution. Anyways thank you so much for your patience. Let me show you what I have done so far. 

    I am building an application that interacts with Active Directory using System.Management.Automation (Not using Directory Services because currently new to that library and learning it). To update the group membership of for a group in the active directory I am creating a JSON object on my view and invoking a function to pass the object & the URI from front end to back end via a function in my controller.

    The basic idea is to allow removal of AD group members in bulk by passing the JSON object as a parameter to the shell script which will be executed in an instance of PowerShell created in the function. I am using .ajax call to invoke the controller function and passing the JSON object that I generated as an argument along with the current URI. The shell.commands.AddParameter() function accepts argument in only string format. So, I typecasted it with ToString() and converting it to JSON in the PowerShell script. I am passing the URL from code behind as the URL is subject to change. I am not getting any errors However, I am also not able to see any update in membership in the AD. Json Object is getting generated from HTML Table.

    My shell script

    param($objMemberUpdate, $uri)
    $body = $objMemberUpdate | ConvertTo-JSON
    Invoke-WebRequest -Uri $uri -Method Post -Body $objMemberUpdate

    My Controller Function to Invoke PowerShell Instance and executing Shell Script file from specified location.

    public string UpdateMemberList(JsonResult objMemberUpdate)
        {
            var uri = HttpContext.Request.Url.AbsoluteUri;
            var shell = PowerShell.Create();
            shell.Commands.AddCommand(AppDomain.CurrentDomain.BaseDirectory + "Shell\\Set-ADGroupMembership.ps1").AddParameter(objMemberUpdate.ToString(), uri);
            var results = shell.Invoke();
            shell.Dispose();
            return results.ToString();
        }

    The Ajax Call that I am calling on a button click on my HTML page.

    //Make Array Object to pass in the API For Membership Update
        $("#btnUpdate").click(function () {
            var RemoveMembers = [];
            var RemoveAfter = [];
            var MemberUpdate = {};
            var GroupGUID = "";
            $("table [id*=ddlReqdAdjustment]").each(function () {
                if ($(this).val() != "Keep") {
                    GroupGUID = $(this).parent().parent().children().eq(4)[0].innerText;
                    var date = $(this).parent().parent().children().eq(8)[0].firstElementChild.value;
                    var ObjectGUID = $(this).parent().parent().children().eq(3)[0].innerText + "@@" + $('#ddlDirectory').val();
    
                    if ($(this).val() == "Remove") {
                        var format = ObjectGUID;
                        RemoveMembers.push(format);
                    } else {
                        var format = date + "|" + ObjectGUID;
                        RemoveAfter.push(format);
                    }
                }
            });
            MemberUpdate = {
                "Directory": $('#ddlDirectory').val(),
                "Group": GroupGUID,
                "Remove": RemoveMembers,
                "RemoveAfter": RemoveAfter,
                "ResultFormat": "json",
                "OnBehalfOf": "11112201"            
            };
            console.log(MemberUpdate);
            $.ajax({
                type: "POST",
                url: "/Group/UpdateMemberList",
                data: { objMemberUpdate: MemberUpdate },
                success: function (response) {
                    alert(response.message);
                }
            });

    The selected member in the table is supposed to get removed from the Group whose GroupGUID (ObjectGUID attribute in AD) is mentioned from the AD. However, I ended up getting Parse Error like this :

    System.Management.Automation.ParseException
    HResult=0x80131501
    Message=System error.
    Source=<Cannot evaluate the exception source>
    StackTrace:
    <Cannot evaluate the exception stack trace>

    Tuesday, July 2, 2019 6:26 PM
  • User753101303 posted

    It could be :
    // give the name and then the value to AddParameter
    .AddParameter("objMemberUpdate",objMemberUpdate).AddParameter("uri",uri);

    I believe AddArgument(objMemberUpdate).AddArgument(uri) should work (uses the position rather than the name).

    Your PS script should post $body maybe. Your controller accept a JsonResult rather than an actual object and it seems it calls a PS script that does nothing else than calling the same controller ??? I still don't get how calling IInvoke-WebResquest can do something usefull in your design.

    If using PowerShell I would have expected calls to things such as https://docs.microsoft.com/en-us/powershell/module/addsadministration/?view=win10-ps

    You coul have used this time instead to get some help on how https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.accountmanagement?view=netframework-4.8 works.

    I really wonder if you shouldn't close this thread and get back at the beginning starting with the part of your app that does actual AD changes. Do you have something on that that works ? It might be easier to help if understanding first how AD changes are currently done ? Or you try to call an API that exists already in your company ?

    Wednesday, July 3, 2019 7:41 AM