diff --git a/Odata.psm1 b/Odata.psm1 deleted file mode 100644 index 59b76b5..0000000 --- a/Odata.psm1 +++ /dev/null @@ -1,222 +0,0 @@ -$schematemplate = @' - - - mosd - MyContainer - -{0} - - -{1} - - -'@ -$rbacconfigtemplate = @' - - - -{0} - - -'@ -function New-OdataClass { - param( - [Parameter(Mandatory=$true, Position=0)] - [string] $Name, - [Parameter(Mandatory=$true)] - [string] $PK, - [Parameter(Mandatory=$true)] - [string[]] $Properties - ) - $props = @() - $props += $properties - new-object psobject -property @{ - Name = $name - PK = $PK - Properties = $props - } -} - -function Set-OdataMethod { - param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [PSObject[]] $InputObject, - [Parameter(Mandatory=$true)] - [ValidateSet("GET","UPDATE","DELETE","CREATE")] - [string] $Verb, - [Parameter(Mandatory=$true)] - [ValidateScript({(get-command $_).modulename})] - [string] $Cmdlet, - [Parameter(Mandatory=$false)] - [alias("Params")] - [string[]] $Parameters, - [Parameter(Mandatory=$false)] - [alias("FilterParams")] - [string[]] $FilterParameters - ) - PROCESS { - $method = new-object psobject -Property @{ - Cmdlet = $cmdlet - Module = 'c:\windows\system32\WindowsPowerShell\v1.0\Modules\{0}' -f (get-command $Cmdlet).Modulename - Parameters = $Parameters - FilterParameters = $FilterParameters - } - $InputObject |add-member -NotePropertyName $verb -NotePropertyValue $method -force - } -} - -function New-OdataEndpoint { - param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [PsObject[]] $OdataClasses, - [Parameter(Mandatory=$false, position=0)] - [string]$Path = "odata", - [Switch]$Force - ) - BEGIN { - if ((Test-Path $Path) -and !$Force) { - throw "Cannot create the endpoint because $Path already exists. Either delete the contents or use the -Force parameter" - } - if (!(Test-Path $Path)) { - mkdir $Path |out-null - } - $classes = @() - } - PROCESS { - $classes += $OdataClasses - } - END { - $mof = "" - $modulestring = "" - $resourcestring = "" - $classstring = "" - foreach ($class in $classes) { - $mof += ConvertTo-MofText $class - $resourcestring += ConvertTo-ResourceXML $class - $classstring += ConvertTo-ClassXML $class - $usedmodules = @() - foreach ($verb in @('get','update','delete','create')) { - if ($class.($verb)) { - if ($usedmodules -notcontains $class.($verb).module) { - $modules += "{0}`r`n" -f $class.($verb).module - $usedmodules += $class.($verb).module - } - } - } - } - $mof |out-file -Encoding ASCII (Join-Path $Path "schema.mof") - $rbacconfigtemplate -f $modules |out-file -Encoding ASCII (Join-Path $Path "RbacConfiguration.xml") - $schematemplate -f $resourcestring, $classstring |out-file -Encoding ASCII (Join-Path $path "schema.xml") - } -} - -# Helper functions - not exported -function ConvertTo-ResourceXML { - param( - [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [ValidateScript({$_.name})] - [psobject] $class - ) - $text = @' - - {0} - mosd_{0} - - -'@ - $text -f $class.name -} - -function ConvertTo-ClassXML{ - param( - [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [ValidateScript({$_.name})] - [psobject] $class - ) - $text = @" - - mosd_$($class.name) - - -"@ - foreach ($verb in @('Get','Update','Delete','Create')) { - if ($class.($verb)) { - # odata uses for the get section - if ($verb -eq 'Get') { - $section = 'Query' - } else { - $section = $verb - } - $text += " "*8 + ("<{0}>`r`n" -f $section) - $text += " "*10 + ("{0}`r`n" -f $class.($verb).Cmdlet) - $text += $verbtext -f $section, $class.($verb).Cmdlet - - # Add Parameters/Options section - if ($class.($verb).Parameters) { - $paramtext = " "*10 + "`r`n" - foreach ($parameter in ($class.($verb).Parameters)) { - $paramtext += (" "*12 + "{0}`r`n") -f $parameter - } - $text += $paramtext + " "*10 + "`r`n" - } - - # Add Filter parameters/FieldParameterMap section - if ($class.($verb).FilterParameters) { - $paramtext = " "*10 + "`r`n" - $paramtext += " "*12 + "`r`n" - foreach ($parameter in ($class.($verb).FilterParameters)) { - $paramtext += (" "*14 + "{0}`r`n") -f $parameter - $paramtext += (" "*14 + "{0}`r`n") -f $parameter - } - $paramtext += " "*12 + "`r`n" - $paramtext += " "*10 + "`r`n" - $text += $paramtext - } - $allparams = $class.($verb).Parameters + $class.($verb).FilterParameters |select -Unique - if ($allparams) { - $paramtext = " "*10 + "`r`n" - $paramtext += " "*12 + "`r`n" - $paramtext += " "*14 + "Default`r`n" - foreach ($parameter in ($allparams)) { - $paramtext += " "*14 + "`r`n" - $paramtext += (" "*16 + "{0}`r`n") -f $parameter - $paramtext += " "*16 + "System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`r`n" - $paramtext += " "*14 + "`r`n" - } - $paramtext += " "*12 + "`r`n" - $paramtext += " "*10 + "`r`n" - $text += $paramtext - } - $text += " "*8 + "`r`n" - } - } - $text += @" - - - -"@ - $text -} - -function ConvertTo-MofText { - param( - [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [ValidateScript({$_.name -and $_.pk -and $_.properties})] - [psobject] $class - ) - # We need to support a pk that may or may not be in the list of properties - $text = @' -class mosd_{0} -{{ - [Key] String {1}; - -'@ - $text = $text -f $class.name, $class.pk - foreach ($property in $class.properties) { - if ($property -ne $class.pk) { - $text += " String $property;`r`n" - } - } - $text += "};`r`n" - $text -} diff --git a/Odata.psd1 b/PshOdata.psd1 similarity index 98% rename from Odata.psd1 rename to PshOdata.psd1 index 2341abf..c1e61a5 100644 Binary files a/Odata.psd1 and b/PshOdata.psd1 differ diff --git a/PshOdata.psm1 b/PshOdata.psm1 new file mode 100644 index 0000000..6faeec5 --- /dev/null +++ b/PshOdata.psm1 @@ -0,0 +1,446 @@ +$schematemplate = @' + + + mosd + MyContainer + +{0} + + +{1} + + +'@ +$rbacconfigtemplate = @' + + + +{0} + + +'@ + +# Following is a list of parameternames to ignore when creating the parameterlist section in schema.xml +$paramignorelist = @('PipelineVariable','OutBuffer','OutVariable','WarningVariable','ErrorVariable','WarningAction', + 'ErrorAction','Debug','Verbose') + +function New-PshOdataClass { +<# + .Synopsis + Creates a PowerShell representation of an Odata class + + .Description + In order to create an odata endpoint, you must first create a class, add GET, UPDATE, CREATE, and/or DELETE + methods for the class, and finally generate the odata files by creating the odata endpoint. + + New-PshOdataClass is used to create the odata class. It specifies what properties are available. This acts + as a sort of Select-Object for the GET method that you will create for the class. + + .Parameter Name + The name of the class. This will be the name used in your odata url, e.g., http://server/odata/classname + + .Parameter PK + Every odata endpoint must have a primary key. This must be a unique identifier for the class. If the cmdlet + you are using does not have a unique value, you will need to wrap the cmdlet in another cmdlet that will add + a primary key before you can create a get method for the class. + + .Parameter Properties + This is the list of properties that will be available to the class. This can be thought of as a Select-Object + after your get- cmdlet. Regardless of what your cmdlet returns, only the properties listed will be visible + when viewing the objects for the class. + + .Inputs + Strings + + .Outputs + PSObject + + .Example + The following is a class that can be used to represent a ProcessInfo object. This class may be used with + a GET method that is returned by the Get-Process cmdlet. + + New-PshOdataClass Process -PK ID -Properties 'Name','ID' + + .LINK + https://github.com/toenuff/PshOdata/ + +#> + param( + [Parameter(Mandatory=$true, Position=0)] + [string] $Name, + [Parameter(Mandatory=$true)] + [string] $PK, + [Parameter(Mandatory=$true)] + [string[]] $Properties + ) + $props = @() + $props += $properties + new-object psobject -property @{ + Name = $name + PK = $PK + Properties = $props + } +} + +function Set-PshOdataMethod { +<# + .Synopsis + This cmdlet adds a GET, DELETE, UPDATE, or CREATE method to the class + + .Description + This cmdlet binds one of the HTTP verbs, i.e., GET, DELETE, UPDATE, or CREATE, to a cmdlet that is installed on the + local system. The cmdlet must be in a module in order for it to work. The method will be added to a PshOdataClass + PSObject that is created by New-PshOdataClass. + + .Parameter InputObject + This is the class that you are setting the method for. This is generally the object returned from New-PshOdataClass + + .Parameter Verb + This is the HTTP verb for the method you are setting. It must be either GET, DELETE, UPDATE, or CREATE. + + Currently only GET and DELETE have been proven to work + + .Parameter Params + This is only supported with the Get method. It will allow you to pass a specific parameter to a cmdlet via the + following syntax in the url: + + http://servername/odata/classname?paramname=value + + .Parameter FilterParams + This is a special parameter that can be used with odata filters. Filtering can be done with any property of the + Odata class. However, if you use a filter parameter, it will ensure that the filter is applied by calling the + associated cmdlet with the parametername specified. This only works with the GET method. + + This will allow you to use the following url: + + http://servername/odata/classname?$filter=(FilterParam eq 'value') + This will call + cmdletname -FilterParam value + + Delete methods should have a FilterParam for the primary key specified in order for Delete to work. If your + cmdlet does not take the PK as a property, you will need to wrap the function in another cmdlet that will accept + the PK. + + .Parameter Cmdlet + This is the cmdlet that the method will invoke under the covers. The cmdlet must be in a module in order for it to + work. + + .Inputs + PSObject + + .Outputs + PSObject + + .Example + The following will add a get method that uses get-process. It will allow name and ID parameters, and it will + allow Name to be used as a parameter if a filter is specified in the URL. + + $class |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name + + The above will allow the following urls: + http://servername/odata/Process + http://servername/odata/Process?Name=notepad + http://servername/odata/Process?ID=3212 + + .Example + The following will create a delete method that runs stop-process: + + $class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID + + The above will allow the delete verb to be passed to the following URL: + http://servername/odata/Process('3333') + + .LINK + https://github.com/toenuff/PshOdata/ + +#> + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [PSObject[]] $InputObject, + [Parameter(Mandatory=$true)] + [ValidateSet("GET","UPDATE","DELETE","CREATE")] + [string] $Verb, + [Parameter(Mandatory=$true)] + [ValidateScript({(get-command $_).modulename})] + [string] $Cmdlet, + [Parameter(Mandatory=$false)] + [alias("Params")] + [string[]] $Parameters, + [Parameter(Mandatory=$false)] + [alias("FilterParams")] + [string[]] $FilterParameters + ) + PROCESS { + if ($verb -eq 'delete') { + # Delete only appears to work with the pk in the fieldparameterset. + # It actually doesn't work with parameters at all + if ($Parameters.count -gt 0 -or $FilterParameters.count -gt 1 -or $FilterParameters[0] -ne $InputObject.pk) { + throw "DELETE methods can only use the primary key as a FieldParameter and they cannot use parameters. + Try Set-PshOdataMethod -verb DELETE -FilterParameters $($InputObject.pk)" + } + } + $method = new-object psobject -Property @{ + Cmdlet = $cmdlet + Module = 'c:\windows\system32\WindowsPowerShell\v1.0\Modules\{0}' -f (get-command $Cmdlet).Modulename + Parameters = $Parameters + FilterParameters = $FilterParameters + } + $InputObject |add-member -NotePropertyName $verb -NotePropertyValue $method -force + } +} + +function New-PshOdataEndpoint { +<# + .Synopsis + Creates an odata endpoint from a collection of defined Odata class objects with methods + + .Description + This cmdlet is used in conjunction with New-PshOdataClass and Set-PshOdataMethod. When New-PshOdataEndpoint is called, + the following three files are created: + + Schema.mof - a document that describes the properties and PK for the classes in the endpoint. + Schema.xml - a document that describes details about the methods and underlying PowerShell cmdlets that will be called through the endpoint. + RbacConfiguration.xml - a document that describes which modules to load. This set of cmdlets assumes that the underlying modules are + located in c:\windows\system32\WindowsPowerShell\modules. + + These files need to be manually copied to the folder of an IIS server that is configured with an application that is using the Odata IIS extensions. + An IISreset is also required. + + .Parameter Path + The folder where you would like to save the schema.mof, schema.xml, and RbacConfiguration.xml files too. This defaults to an odata folder in + the current working directory + + .Parameter PshOdataClasses + A collection of PshOdata classes with methods that are set for the classes. This generally comes from the output of New-PshOdataClass and + Set-PshOdataMethod. + + .Parameter Force + This is used to overwrite the output files if they already exist. + + .Inputs + PSObject + + .Outputs + Three files: Schema.mof, Schema.xml, and RbaConfiguration + + .Example + $class = New-PshOdataClass Process -PK ID -Properties 'Name','ID' + $class |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name + $class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID + $class | New-PshOdataEndpoint + + The above will create the files required to allow GET and SET http methods to a url like this: + http://server/odata/Process + http://server/odata/Process('3333') # 3333 is the ID of the process you would like to retrieve. This is the only url that works for delete. + http://server/odata/Process?name=notepad + http://server/odata/Process?$filter=(name eq 'notepad') + http://server/odata/Process?$format=application/json;odata=verbose #Used to render JSON instead of XML + + The endpoint will return Process objects that contain Name and ID Properties that are taken from Get-Process. It will also allow the DELETE + method to call Stop-Process when the PK is used. + + .Example + The following creates the files required for an Odata Endpoint that serves Process and Service objects that are returned from Get-Process and Get-Service + $classes = @() + $classes += New-PshOdataClass Process -PK ID -Properties 'Name', 'ID' |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name + $classes += New-PshOdataClass Service -PK Name -Properties 'Status', 'Name', 'Displayname' |Set-PshOdataMethod -verb get -cmdlet get-Service -Params Name -FilterParams Name + $classes |New-PshOdataEndpoint + + .Example + The following script will create the files required for an Odata endpoint directly into c:\inetpub\wwwroot\odata. If the files exist already, + they will be overwritten. + + $class |New-PshOdataEndpoint -Path c:\inetpub\wwwroot\odata -Force + + .LINK + https://github.com/toenuff/PshOdata/ + +#> + param( + [Parameter(Mandatory=$false, ValueFromPipeline=$true)] + [PsObject[]] $PshOdataClasses, + [Parameter(Mandatory=$false, position=0)] + [string]$Path = "odata", + [Switch]$Force + ) + BEGIN { + if ((Test-Path $Path) -and !$Force) { + throw "Cannot create the endpoint because $Path already exists. Either delete the contents or use the -Force parameter" + } + if (!(Test-Path $Path)) { + mkdir $Path |out-null + } + $classes = @() + } + PROCESS { + $classes += $PshOdataClasses + } + END { + $mof = "" + $modulestring = "" + $resourcestring = "" + $classstring = "" + foreach ($class in $classes) { + $mof += ConvertTo-MofText $class + $resourcestring += ConvertTo-ResourceXML $class + $classstring += ConvertTo-ClassXML $class + $usedmodules = @() + foreach ($verb in @('get','update','delete','create')) { + if ($class.($verb)) { + if ($usedmodules -notcontains $class.($verb).module) { + $modules += "{0}`r`n" -f $class.($verb).module + $usedmodules += $class.($verb).module + } + } + } + } + $mof |out-file -Encoding ASCII (Join-Path $Path "schema.mof") + $rbacconfigtemplate -f $modules |out-file -Encoding ASCII (Join-Path $Path "RbacConfiguration.xml") + $schematemplate -f $resourcestring, $classstring |out-file -Encoding ASCII (Join-Path $path "schema.xml") + } +} + +# Helper functions - not exported +function ConvertTo-ResourceXML { + param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateScript({$_.name})] + [psobject] $class + ) + $text = @' + + {0} + mosd_{0} + + +'@ + $text -f $class.name +} + +function ConvertTo-ClassXML{ + param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateScript({$_.name})] + [psobject] $class + ) + $text = @" + + mosd_$($class.name) + + +"@ + foreach ($verb in @('Get','Update','Delete','Create')) { + if ($class.($verb)) { + # odata uses for the get section + if ($verb -eq 'Get') { + $section = 'Query' + } else { + $section = $verb + } + $text += " "*8 + "<{0}>`r`n" -f $section + $text += " "*10 + "{0}`r`n" -f $class.($verb).Cmdlet + $text += $verbtext -f $section, $class.($verb).Cmdlet + + # Add Parameters/Options section + if ($class.($verb).Parameters) { + $paramtext = " "*10 + "`r`n" + foreach ($parameter in ($class.($verb).Parameters)) { + $paramtext += " "*12 + "{0}`r`n" -f $parameter + } + $text += $paramtext + " "*10 + "`r`n" + } + + $paramnames = (get-command $class.($verb).Cmdlet |select -ExpandProperty Parameters).keys + # Add Filter parameters/FieldParameterMap section + if ($class.($verb).FilterParameters) { + $paramtext = " "*10 + "`r`n" + foreach ($parameter in ($class.($verb).FilterParameters)) { + $targetparameter = $parameter + # FieldParameters are case sensitive on the target parameter + if ($paramnames -cnotcontains $parameter) { + if ($paramnames -contains $parameter) { + foreach ($name in $paramnames) { + if ($parameter -eq $name) { + # Set it blank first otherwise it won't take the case change + $targetparameter = $name + } + } + } else { + throw "{0} parameter does not exist in {1}" -f $parameter, $section, $class.($verb).Cmdlet + } + } + $paramtext += " "*12 + "`r`n" + $paramtext += " "*14 + "{0}`r`n" -f $parameter + $paramtext += " "*14 + "{0}`r`n" -f $targetparameter + $paramtext += " "*12 + "`r`n" + } + $paramtext += " "*10 + "`r`n" + $text += $paramtext + } + $allparams = $class.($verb).Parameters + $class.($verb).FilterParameters |select -Unique + $text += Get-ParameterSetXML $class.($verb).cmdlet + $text += " "*8 + "`r`n" + } + } + $text += @" + + + +"@ + $text +} + +function ConvertTo-MofText { + param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateScript({$_.name -and $_.pk -and $_.properties})] + [psobject] $class + ) + # We need to support a pk that may or may not be in the list of properties + $text = @' +class mosd_{0} +{{ + [Key] String {1}; + +'@ + $text = $text -f $class.name, $class.pk + foreach ($property in $class.properties) { + if ($property -ne $class.pk) { + $text += " String $property;`r`n" + } + } + $text += "};`r`n" + $text +} + +function Get-ParameterSetXML { + param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateScript({get-command $_})] + [String] $Cmdlet + ) + $text = " "*10 + "`r`n" + foreach ($parameterset in (get-command $cmdlet |select -ExpandProperty parametersets)) { + $text += " "*12 + "`r`n" + $text += " "*14 + "{0}`r`n" -f $parameterset.name + foreach ($parameter in ($parameterset.Parameters)) { + if ($paramignorelist -notcontains $parameter.name) { + $text += " "*14 + "`r`n" + $text += " "*16 + "{0}`r`n" -f $parameter.name + if ($parameter.ParameterType.ToString() -eq 'System.Management.Automation.SwitchParameter') { + $text += " "*16 + "System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`r`n" + $text += " "*16 + "True`r`n" + } else { + $text += " "*16 + "System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`r`n" + } + if ($parameter.IsMandatory) { + $text += " "*16 + "True`r`n" + } + $text += " "*14 + "`r`n" + } + } + $text += " "*12 + "`r`n" + } + $text += " "*10 + "`r`n" + $text +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ede891 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# PshOdata Module + +2008R2 and 2012 versions of Windows contain a feature called IIS Odata Extensions. An IIS application that uses the extensions can be configured to create a RESTful web service that will run PowerShell cmdlets and return the objects as either JSON or XML. The PshOdata Module makes it easy to generate the files used to create these endpoints. + +## Example Usage +> $class |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name +> $class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID +> $class | New-PshOdataEndpoint + +The above will create the files required to allow GET and SET http methods to a url like this: +* http://server/odata/Process +* http://server/odata/Process('3333') # 3333 is the ID of the process you would like to retrieve. This is the only url that works for delete. +* http://server/odata/Process?name=notepad +* http://server/odata/Process?$filter=(name eq 'notepad') +* http://server/odata/Process?$format=application/json;odata=verbose #Used to render JSON instead of XML + +The endpoint will return Process objects that contain Name and ID Properties that are taken from Get-Process. It will also allow the DELETE +method to call Stop-Process when the PK is used. + +## Files Generated + +The New-PshOdataEndpoint function will create a set of files in an odata folder within the current working directory by default. + +The following files are generated: +* Schema.mof - a document that describes the properties and PK for the classes in the endpoint. +* Schema.xml - a document that describes details about the methods and underlying PowerShell cmdlets that will be called through the endpoint. +* RbacConfiguration.xml - a document that describes which modules to load. This set of cmdlets assumes that the underlying modules are located in c:\windows\system32\WindowsPowerShell\modules. + +## Installation of the files + +Currently, we do not have a method to create the IIS portion of the Odata extensions. We plan on solving this with a function soon. However, in the meantime, you can use the OdataSchemaDesigner in order to have your first endpoint created, and then you can use these cmdlets to generate the files you need for your Odata service. + +The following steps must be performed on Windows Server 2012 box that does NOT have R2. + +1. Add-WindowsFeature ManagementOdata # Install the odata extensions +1. Install Visual Studio Isolated Shell - http://www.microsoft.com/en-us/download/details.aspx?id=1366 +1. Install the odata extension isolated installer - http://archive.msdn.microsoft.com/mgmtODataWebServ/Release/ProjectReleases.aspx?ReleaseId=5877 +1. Launch the Management Odata Schema Designer from the start screen + 1. File-> New File -> Management Odata Model + 1. Right-Click and select Import Cmdlets + 1. Local Computer -> Next + 1. Installed Windows PowerShell Modules -> Microsoft.PowerShell.Management + 1. Choose Service -> Next + 1. Uncheck CREATE and UPDATE. Only Get should be selected. Next. + 1. Next + 1. Choose any key and click Next. + 1. Next + 1. Finish + 1. Right click in the designer and choose Publish Odata Endpoint + 1. Select the local computername and fill out a username and password. Choose a name for the site (odata is a good choice). Finally select a port number and then click Publish. + +Once the IIS server has the site created. You can use the cmdlets in this module to generate the schema.xml, schema.mof, and rbacconfiguration.xml files. Copy these files into the application you created (c:\inetpub\wwwroot\odata). Perform an IISReset. Enjoy your web service. + +## Notes + +* Currently only GET and DELETE is supported +* Use *Get-Command -Module PshOdata* in order to see the cmdlets in the module +* Use *Get-Help cmdletname -full* in order to see the full set of documenation along with examples you can try +* Odata classes require a primary key. This needs to be a unique value for each object returned. If one does not exist for the data you are retrieving from PowerShell, you will need to create a wrapper function in a new module that calls your function and creates a primary key. You can then create a method based on this new cmdlet. +* When creating a DELETE method, you may only use the PK as a parameter. Your delete cmdlet must support this. diff --git a/license b/license new file mode 100644 index 0000000..6d84f0b --- /dev/null +++ b/license @@ -0,0 +1,8 @@ +Copyright (C) 2014 Tome Tanasovski + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/tests/Odata.tests.ps1 b/tests/Odata.tests.ps1 deleted file mode 100644 index d5bc6d5..0000000 --- a/tests/Odata.tests.ps1 +++ /dev/null @@ -1,170 +0,0 @@ -$here = (Split-Path (Split-Path -Parent $MyInvocation.MyCommand.Path)) - -Invoke-Expression (gc "$here\odata.psm1" |out-String) - -$class = New-OdataClass Process -PK ID -Properties 'Name','ID' - -Describe "New-OdataClass" { - It "Returns a psobject with the provided name" { - $class.name | Should Be "Process" - } - It "Returns a psobject with the provided PK" { - $class.pk | Should Be "ID" - } - It "Returns a psobject with two properties" { - $class.properties.count |Should Be 2 - } - It "New-OdataClass with only one property should return a list with one element in it" { - (New-OdataClass TestClass -PK ID -Properties 'ID').properties.count |Should Be 1 - } -} - -Describe "Add-OdataMethod" { - It "Validate that optional parameters are optional" { - {$class |set-odatamethod -verb get -cmdlet get-process} |should not Throw - } - It "Creates a GET method" { - {$class |set-odatamethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name} |should not Throw - } - It "Sets the get property of the class" { - $class.get |should not BeNullOrEmpty - } - It "Validates the get property's module property" { - $class.get.module |Should be "c:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management" - } - It "Should not set the update property of the class when only the get method was added" { - $class.update|should BeNullOrEmpty - } - It "Creates a DELETE method" { - $class |set-odatamethod -verb delete -cmdlet stop-process -FilterParams ID -params ID - } - It "Fails if verb is not valid" { - {$class |set-odatamethod -verb blah -cmdlet dir -pk ID -Params Name, ID -FilterParams Name} |should Throw - } - It "Fails if cmdlet is not valid" { - {$class |set-odatamethod -verb get -cmdlet dir -pk ID -Params Name, ID -FilterParams Name} |should Throw - } -} - -Describe "ConvertTo-MofText" { - It "Converts a class to the text required in a Mof file" { - $class |ConvertTo-MofText |Should be @' -class mosd_Process -{ - [Key] String ID; - String Name; -}; - -'@ - } -} - -Describe "ConvertTo-ResourceXML" { - It "Converts a class to the text needed in the resource section of the schema.xml" { - $class |ConvertTo-ResourceXML |Should be @' - - Process - mosd_Process - - -'@ - } -} - -Describe "ConvertTo-ClassXML" { - It "Converts a class to the text needed in the class section of the schema.xml" { - $class |ConvertTo-ClassXML |Should be @' - - mosd_Process - - - get-process - - Name - ID - - - - Name - Name - - - - - Default - - Name - System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ID - System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - - stop-process - - ID - - - - ID - ID - - - - - Default - - ID - System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - - - -'@ - } -} - -Describe "New-OdataEndpoint" { - It "New-OdataEndpoint succeeds if the folder exists and the -Force switch is used" { - {$class |New-OdataEndpoint} |Should Not Throw - } - It "New-OdataEndpoint should fail if the folder exists" { - {$class |New-OdataEndpoint} |Should Throw - } - It "New-OdataEndpoint succeeds if the folder exists and the -Force switch is used" { - {$class |New-OdataEndpoint -Force} |Should Not Throw - } - It "Should create schema.mof" { - join-path odata schema.mof |Should exist - } - It "Should create schema.xml" { - join-path odata schema.xml |Should exist - } - It "Should create RbacConfiguration.xml" { - join-path odata RbacConfiguration.xml |Should exist - } - It "Validate data in RbacConfiguration.xml" { - [IO.file]::ReadAllText((join-path odata RbacConfiguration.xml)) |Should be @" - - - -c:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management - - - - -"@ - } -} - -# cleanup the directory created during the tests -rm (join-path $here odata) -recurse - -#TODO consider moving validation text into validator files in the /tests directory diff --git a/tests/PshOdata.tests.ps1 b/tests/PshOdata.tests.ps1 new file mode 100644 index 0000000..0a0c2e9 --- /dev/null +++ b/tests/PshOdata.tests.ps1 @@ -0,0 +1,177 @@ +$here = '' +if ($MyInvocation.MyCommand.Path) { + $here = Split-Path $MyInvocation.MyCommand.Path +} else { + $here = $pwd -replace '^\S+::','' +} +$odatapath = join-path $here "odata" + +Invoke-Expression (gc "$here\..\PshOdata.psm1" |out-String) + +$class = New-PshOdataClass Process -PK ID -Properties 'Name','ID' + +Describe "New-PshOdataClass" { + It "Returns a psobject with the provided name" { + $class.name | Should Be "Process" + } + It "Returns a psobject with the provided PK" { + $class.pk | Should Be "ID" + } + It "Returns a psobject with two properties" { + $class.properties.count |Should Be 2 + } + It "New-PshOdataClass with only one property should return a list with one element in it" { + (New-PshOdataClass TestClass -PK ID -Properties 'ID').properties.count |Should Be 1 + } +} + +Describe "Add-PshOdataMethod" { + It "Validate that optional parameters are optional" { + {$class |Set-PshOdataMethod -verb get -cmdlet get-process} |should not Throw + } + It "Creates a GET method" { + {$class |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name} |should not Throw + } + It "Sets the get property of the class" { + $class.get |should not BeNullOrEmpty + } + It "Validates the get property's module property" { + $class.get.module |Should be "c:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management" + } + It "Should not set the update property of the class when only the get method was added" { + $class.update|should BeNullOrEmpty + } + It "Creates a DELETE method" { + {$class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID} |should not Throw + } + It "Fails if the DELETE method has a FieldParam that is not the pk" { + {$class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams Name} |Should Throw + } + It "Fails if the DELETE method has a value for the params argument" { + {$class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID -params ID,Name} |Should Throw + } + It "Fails if verb is not valid" { + {$class |Set-PshOdataMethod -verb blah -cmdlet dir -pk ID -Params Name, ID -FilterParams Name} |should Throw + } + It "Fails if cmdlet is not valid" { + {$class |Set-PshOdataMethod -verb get -cmdlet dir -pk ID -Params Name, ID -FilterParams Name} |should Throw + } +} + +function CommandWith2ParamSets { + param( + [Parameter(Mandatory=$true,ParameterSetName="set1")] + [string] $set1param, + [Parameter(Mandatory=$true,ParameterSetName="set2")] + [string] $set2param, + [Parameter(Mandatory=$false)] + [switch] $switchparam + ) +} +Describe "Get-ParameterSetXML" { + It "Does not work with an invalid command" { + {Get-ParameterSetXML 'lskdfjlsdfjlksdfjljdfkljdsf'} |should Throw + } + It "Converts the ParameterSet of a command into appropriate schema.xml syntax" { + Get-ParameterSetXML CommandWith2ParamSets |Should be @' + + + set1 + + set1param + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + switchparam + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + set2 + + set2param + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + switchparam + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + +'@ + } +} + +Describe "ConvertTo-MofText" { + It "Converts a class to the text required in a Mof file" { + $class |ConvertTo-MofText |Should be @' +class mosd_Process +{ + [Key] String ID; + String Name; +}; + +'@ + } +} + +Describe "ConvertTo-ResourceXML" { + It "Converts a class to the text needed in the resource section of the schema.xml" { + $class |ConvertTo-ResourceXML |Should be @' + + Process + mosd_Process + + +'@ + } +} + +$validatexml = [IO.file]::ReadAllText((join-path $here validate_schema.xml)) +Describe "ConvertTo-ClassXML" { + It "Converts a class to the text needed in the class section of the schema.xml" { + $class |ConvertTo-ClassXML |Should BeExactly $validatexml + } +} + +Describe "New-PshOdataEndpoint" { + It "New-PshOdataEndpoint succeeds if the folder exists and the -Force switch is used" { + {$class |New-PshOdataEndpoint -Path $odatapath} |Should Not Throw + } + It "New-PshOdataEndpoint should fail if the folder exists" { + {$class |New-PshOdataEndpoint -Path $odatapath} |Should Throw + } + It "NewPshOdataEndpoint succeeds if the folder exists and the -Force switch is used" { + {$class |New-PshOdataEndpoint -Path $odatapath -Force} |Should Not Throw + } + It "Should create schema.mof" { + join-path $odatapath schema.mof |Should exist + } + It "Should create schema.xml" { + join-path $odatapath schema.xml |Should exist + } + It "Should create RbacConfiguration.xml" { + join-path $odatapath RbacConfiguration.xml |Should exist + } + It "Validate data in RbacConfiguration.xml" { + [IO.file]::ReadAllText((join-path $odatapath RbacConfiguration.xml)) |Should be @" + + + +c:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management + + + + +"@ + } +} + +# cleanup the directory created during the tests +rm $odatapath -recurse + diff --git a/tests/validate_schema.xml b/tests/validate_schema.xml new file mode 100644 index 0000000..2d76b7a --- /dev/null +++ b/tests/validate_schema.xml @@ -0,0 +1,221 @@ + + mosd_Process + + + get-process + + Name + ID + + + + Name + Name + + + + + Name + + Name + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ComputerName + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Module + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + FileVersionInfo + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + NameWithUserName + + Name + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IncludeUserName + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + True + + + + IdWithUserName + + Id + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + IncludeUserName + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + True + + + + Id + + Id + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + ComputerName + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Module + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + FileVersionInfo + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + InputObjectWithUserName + + InputObject + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + IncludeUserName + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + True + + + + InputObject + + InputObject + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + ComputerName + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Module + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + FileVersionInfo + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + + + stop-process + + + ID + Id + + + + + Id + + Id + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + PassThru + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Force + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + WhatIf + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Confirm + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + Name + + Name + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + PassThru + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Force + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + WhatIf + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Confirm + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + InputObject + + InputObject + System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True + + + PassThru + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Force + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + WhatIf + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + Confirm + System.Management.Automation.SwitchParameter, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + True + + + + + +