diff --git a/docs/KNOWNISSUES.md b/docs/KNOWNISSUES.md index 5b6587183..3454cb611 100644 --- a/docs/KNOWNISSUES.md +++ b/docs/KNOWNISSUES.md @@ -15,10 +15,8 @@ with `PlatformNotSupportedException`. ## `ControlPanelItemCommand.cs` -The file `monad/src/commands/management/ControlPanelItemCommand.cs` has been removed -temporarily from `Microsoft.PowerShell.Commands.Management` because we -cannot resolve `[Shell32.ShellFolderItem]` for FullCLR builds. This must be -fixed ASAP. +The file `ControlPanelItemCommand.cs` is excluded from all frameworks in `Microsoft.PowerShell.Commands.Management` +because it has dependency on `[Shell32.ShellFolderItem]` for FullCLR builds. ## `GetComputerInfoCommand.cs` diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs new file mode 100644 index 000000000..5e39e9d6f --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -0,0 +1,769 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Microsoft.Win32; +using Shell32; +using System; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Collections.Generic; +using Dbg = System.Management.Automation.Diagnostics; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Represent a control panel item + /// + public sealed class ControlPanelItem + { + /// + /// Control panel applet name + /// + public string Name + { + get { return _name; } + } + private string _name; + + /// + /// Control panel applet canonical name + /// + public string CanonicalName + { + get { return _canonicalName; } + } + private string _canonicalName; + + /// + /// Control panel applet category + /// + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Category + { + get { return _category; } + } + private string[] _category; + + /// + /// Control panel applet description + /// + public string Description + { + get { return _description; } + } + private string _description; + + /// + /// Control panel applet path + /// + internal string Path + { + get { return _path; } + } + private string _path; + + /// + /// Internal constructor for ControlPanelItem + /// + /// + /// + /// + /// + /// + internal ControlPanelItem(string name, string canonicalName, string[] category, string description, string path) + { + _name = name; + _path = path; + _canonicalName = canonicalName; + _category = category; + _description = description; + } + + /// + /// ToString method + /// + /// + public override string ToString() + { + return this.Name; + } + } + + /// + /// This class implements the base for ControlPanelItem commands + /// + public abstract class ControlPanelItemBaseCommand : PSCmdlet + { + /// + /// Locale specific verb action Open string exposed by the control panel item. + /// + private static string VerbActionOpenName = null; + + /// + /// Canonical name of the control panel item used as a refernece to fetch the verb + /// action Open string. This control panel item exists on all SKU's. + /// + private const string RegionCanonicalName = "Microsoft.RegionAndLanguage"; + + private const string ControlPanelShellFolder = "shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}"; + private static readonly string[] ControlPanelItemFilterList = new string[] { "Folder Options", "Taskbar and Start Menu" }; + private const string TestHeadlessServerScript = @" +$result = $false +$serverManagerModule = Get-Module -ListAvailable | ? {$_.Name -eq 'ServerManager'} +if ($serverManagerModule -ne $null) +{ + Import-Module ServerManager + $Gui = (Get-WindowsFeature Server-Gui-Shell).Installed + if ($Gui -eq $false) + { + $result = $true + } +} +$result +"; + internal readonly Dictionary CategoryMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + internal string[] CategoryNames = { "*" }; + internal string[] RegularNames = { "*" }; + internal string[] CanonicalNames = { "*" }; + internal ControlPanelItem[] ControlPanelItems = new ControlPanelItem[0]; + + /// + /// Get all executable control panel items + /// + internal List AllControlPanelItems + { + get + { + if (_allControlPanelItems == null) + { + _allControlPanelItems = new List(); + string allItemFolderPath = ControlPanelShellFolder + "\\0"; + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 allItemFolder = (Folder2)shell2.NameSpace(allItemFolderPath); + FolderItems3 allItems = (FolderItems3)allItemFolder.Items(); + + bool applyControlPanelItemFilterList = IsServerCoreOrHeadLessServer(); + + foreach (ShellFolderItem item in allItems) + { + if (applyControlPanelItemFilterList) + { + bool match = false; + foreach (string name in ControlPanelItemFilterList) + { + if (name.Equals(item.Name, StringComparison.OrdinalIgnoreCase)) + { + match = true; + break; + } + } + if (match) + continue; + } + + if (ContainVerbOpen(item)) + _allControlPanelItems.Add(item); + } + } + return _allControlPanelItems; + } + } + private List _allControlPanelItems; + + #region Cmdlet Overrides + + /// + /// Does the preprocessing for ControlPanelItem cmdlets + /// + protected override void BeginProcessing() + { + System.OperatingSystem osInfo = System.Environment.OSVersion; + PlatformID platform = osInfo.Platform; + Version version = osInfo.Version; + + if (platform.Equals(PlatformID.Win32NT) && + ((version.Major < 6) || + ((version.Major == 6) && (version.Minor < 2)) + )) + { + // Below Win8, this cmdlet is not supported because of Win8:794135 + // throw terminating + string message = string.Format(CultureInfo.InvariantCulture, + ControlPanelResources.ControlPanelItemCmdletNotSupported, + this.CommandInfo.Name); + throw new PSNotSupportedException(message); + } + } + + #endregion + + /// + /// Test if an item can be invoked + /// + /// + /// + private bool ContainVerbOpen(ShellFolderItem item) + { + bool result = false; + FolderItemVerbs verbs = item.Verbs(); + foreach (FolderItemVerb verb in verbs) + { + if (!String.IsNullOrEmpty(verb.Name) && + (verb.Name.Equals(ControlPanelResources.VerbActionOpen, StringComparison.OrdinalIgnoreCase) || + CompareVerbActionOpen(verb.Name))) + { + result = true; + break; + } + } + return result; + } + + /// + /// CompareVerbActionOpen is a helper function used to perform locale specific + /// comparision of the verb action Open exposed by various control panel items. + /// + /// Locale spcific verb action exposed by the control panel item. + /// True if the control panel item supports verb action open or else returns false. + private static bool CompareVerbActionOpen(string verbActionName) + { + if (VerbActionOpenName == null) + { + const string allItemFolderPath = ControlPanelShellFolder + "\\0"; + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 allItemFolder = (Folder2)shell2.NameSpace(allItemFolderPath); + FolderItems3 allItems = (FolderItems3)allItemFolder.Items(); + + foreach (ShellFolderItem item in allItems) + { + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = !String.IsNullOrEmpty(canonicalName) + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + + if (canonicalName != null && canonicalName.Equals(RegionCanonicalName, StringComparison.OrdinalIgnoreCase)) + { + // The 'Region' control panel item always has '&Open' (english or other locale) as the first verb name + VerbActionOpenName = item.Verbs().Item(0).Name; + break; + } + } + + Dbg.Assert(VerbActionOpenName != null, "The 'Region' control panel item is available on all SKUs and it always " + + "has '&Open' as the first verb item, so VerbActionOpenName should never be null at this point"); + } + + return VerbActionOpenName.Equals(verbActionName, StringComparison.OrdinalIgnoreCase); + } + + /// + /// IsServerCoreORHeadLessServer is a helper function that checks if the current SKU is a + /// Server Core machine or if the Server-GUI-Shell feature is removed on the machine. + /// + /// True if the current SKU is a Server Core machine or if the Server-GUI-Shell + /// feature is removed on the machine or else returns false. + private bool IsServerCoreOrHeadLessServer() + { + bool result = false; + + using (RegistryKey installation = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")) + { + Dbg.Assert(installation != null, "the CurrentVersion subkey should exist"); + + string installationType = (string)installation.GetValue("InstallationType", ""); + + if (installationType.Equals("Server Core")) + { + result = true; + } + else if (installationType.Equals("Server")) + { + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + ps.AddScript(TestHeadlessServerScript); + Collection psObjectCollection = ps.Invoke(new object[0]); + Dbg.Assert(psObjectCollection != null && psObjectCollection.Count == 1, "invoke should never return null, there should be only one return item"); + if (LanguagePrimitives.IsTrue(PSObject.Base(psObjectCollection[0]))) + { + result = true; + } + } + } + } + + return result; + } + + /// + /// Get the category number and name map + /// + internal void GetCategoryMap() + { + if (CategoryMap.Count != 0) + { + return; + } + + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 categoryFolder = (Folder2)shell2.NameSpace(ControlPanelShellFolder); + FolderItems3 catItems = (FolderItems3)categoryFolder.Items(); + + foreach (ShellFolderItem category in catItems) + { + string path = category.Path; + string catNum = path.Substring(path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase) + 1); + + CategoryMap.Add(catNum, category.Name); + } + } + + /// + /// Get control panel item by the category + /// + /// + /// + internal List GetControlPanelItemByCategory(List controlPanelItems) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string pattern in CategoryNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + int[] categories = (int[])item.ExtendedProperty("System.ControlPanel.Category"); + foreach (int cat in categories) + { + string catStr = (string)LanguagePrimitives.ConvertTo(cat, typeof (string), CultureInfo.InvariantCulture); + Dbg.Assert(CategoryMap.ContainsKey(catStr), "the category should be contained in _categoryMap"); + string catName = CategoryMap[catStr]; + + if (!wildcard.IsMatch(catName)) + continue; + if (itemSet.Contains(path)) + { + found = true; + break; + } + + found = true; + itemSet.Add(path); + list.Add(item); + break; + } + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string errMsg = StringUtil.Format(ControlPanelResources.NoControlPanelItemFoundForGivenCategory, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenCategory", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the regular name + /// + /// + /// + /// + internal List GetControlPanelItemByName(List controlPanelItems, bool withCategoryFilter) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string pattern in RegularNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string name = item.Name; + string path = item.Path; + if (!wildcard.IsMatch(name)) + continue; + if (itemSet.Contains(path)) + { + found = true; + continue; + } + + found = true; + itemSet.Add(path); + list.Add(item); + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string formatString = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundForGivenNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundForGivenName; + string errMsg = StringUtil.Format(formatString, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenName", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the canonical name + /// + /// + /// + /// + internal List GetControlPanelItemByCanonicalName(List controlPanelItems, bool withCategoryFilter) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (CanonicalNames == null) + { + bool found = false; + foreach (ShellFolderItem item in controlPanelItems) + { + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + if (canonicalName == null) + { + found = true; + list.Add(item); + } + } + + if (!found) + { + string errMsg = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundWithNullCanonicalNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundWithNullCanonicalName; + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "", + ErrorCategory.InvalidArgument, CanonicalNames); + WriteError(error); + } + return list; + } + + foreach (string pattern in CanonicalNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = canonicalName != null + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + + if (canonicalName == null) + { + if (pattern.Equals("*", StringComparison.OrdinalIgnoreCase)) + { + found = true; + if (!itemSet.Contains(path)) + { + itemSet.Add(path); + list.Add(item); + } + } + } + else + { + if (!wildcard.IsMatch(canonicalName)) + continue; + if (itemSet.Contains(path)) + { + found = true; + continue; + } + + found = true; + itemSet.Add(path); + list.Add(item); + } + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string formatString = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundForGivenCanonicalNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundForGivenCanonicalName; + string errMsg = StringUtil.Format(formatString, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenCanonicalName", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the ControlPanelItem instances + /// + /// + /// + internal List GetControlPanelItemsByInstance(List controlPanelItems) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (ControlPanelItem controlPanelItem in ControlPanelItems) + { + bool found = false; + + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + if (!controlPanelItem.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) + continue; + if (itemSet.Contains(path)) + { + found = true; + break; + } + + found = true; + itemSet.Add(path); + list.Add(item); + break; + } + + if (!found) + { + string errMsg = StringUtil.Format(ControlPanelResources.NoControlPanelItemFoundForGivenInstance, + controlPanelItem.GetType().Name); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenInstance", + ErrorCategory.InvalidArgument, controlPanelItem); + WriteError(error); + } + } + + return list; + } + } + + /// + /// Get all control panel items that is available in the "All Control Panel Items" category + /// + [Cmdlet(VerbsCommon.Get, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=219982")] + [OutputType(typeof(ControlPanelItem))] + public sealed class GetControlPanelItemCommand : ControlPanelItemBaseCommand + { + private const string RegularNameParameterSet = "RegularName"; + private const string CanonicalNameParameterSet = "CanonicalName"; + + #region "Parameters" + + /// + /// Control panel item names + /// + [Parameter(Position = 0, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Name + { + get { return RegularNames; } + set + { + RegularNames = value; + _nameSpecified = true; + } + } + private bool _nameSpecified = false; + + /// + /// Canonical names of control panel items + /// + [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] + [AllowNull] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] CanonicalName + { + get { return CanonicalNames; } + set + { + CanonicalNames = value; + _canonicalNameSpecified = true; + } + } + private bool _canonicalNameSpecified = false; + + /// + /// Category of control panel items + /// + [Parameter] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Category + { + get { return CategoryNames; } + set + { + CategoryNames = value; + _categorySpecified = true; + } + } + private bool _categorySpecified = false; + + #endregion "Parameters" + + /// + /// + /// + protected override void ProcessRecord() + { + GetCategoryMap(); + List items = GetControlPanelItemByCategory(AllControlPanelItems); + + if (_nameSpecified) + { + items = GetControlPanelItemByName(items, _categorySpecified); + } + else if (_canonicalNameSpecified) + { + items = GetControlPanelItemByCanonicalName(items, _categorySpecified); + } + + List results = new List(); + foreach (ShellFolderItem item in items) + { + string name = item.Name; + string path = item.Path; + string description = (string)item.ExtendedProperty("InfoTip"); + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = canonicalName != null + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + int[] categories = (int[])item.ExtendedProperty("System.ControlPanel.Category"); + string[] cateStrings = new string[categories.Length]; + for (int i = 0; i < categories.Length; i++) + { + string catStr = (string)LanguagePrimitives.ConvertTo(categories[i], typeof(string), CultureInfo.InvariantCulture); + Dbg.Assert(CategoryMap.ContainsKey(catStr), "the category should be contained in CategoryMap"); + cateStrings[i] = CategoryMap[catStr]; + } + + ControlPanelItem controlPanelItem = new ControlPanelItem(name, canonicalName, cateStrings, description, path); + results.Add(controlPanelItem); + } + + // Sort the reuslts by Canonical Name + results.Sort(CompareControlPanelItems); + foreach (ControlPanelItem controlPanelItem in results) + { + WriteObject(controlPanelItem); + } + } + + #region "Private Methods" + + private static int CompareControlPanelItems(ControlPanelItem x, ControlPanelItem y) + { + // In the case that at least one of them is null + if (x.CanonicalName == null && y.CanonicalName == null) + return 0; + if (x.CanonicalName == null) + return 1; + if (y.CanonicalName == null) + return -1; + + // In the case that both are not null + return string.Compare(x.CanonicalName, y.CanonicalName, StringComparison.OrdinalIgnoreCase); + } + + #endregion "Private Methods" + } + + /// + /// Show the specified control panel applet + /// + [Cmdlet(VerbsCommon.Show, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=219983")] + public sealed class ShowControlPanelItemCommand : ControlPanelItemBaseCommand + { + private const string RegularNameParameterSet = "RegularName"; + private const string CanonicalNameParameterSet = "CanonicalName"; + private const string ControlPanelItemParameterSet = "ControlPanelItem"; + + #region "Parameters" + + /// + /// Control panel item names + /// + [Parameter(Position = 0, Mandatory = true, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Name + { + get { return RegularNames; } + set { RegularNames = value; } + } + + /// + /// Canonical names of control panel items + /// + [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] + [AllowNull] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] CanonicalName + { + get { return CanonicalNames; } + set { CanonicalNames = value; } + } + + /// + /// Control panel items returned by Get-ControlPanelItem + /// + [Parameter(Position = 0, ParameterSetName = ControlPanelItemParameterSet, ValueFromPipeline = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public ControlPanelItem[] InputObject + { + get { return ControlPanelItems; } + set { ControlPanelItems = value; } + } + + #endregion "Parameters" + + /// + /// + /// + protected override void ProcessRecord() + { + List items; + if (ParameterSetName == RegularNameParameterSet) + { + items = GetControlPanelItemByName(AllControlPanelItems, false); + } + else if (ParameterSetName == CanonicalNameParameterSet) + { + items = GetControlPanelItemByCanonicalName(AllControlPanelItems, false); + } + else + { + items = GetControlPanelItemsByInstance(AllControlPanelItems); + } + + foreach (ShellFolderItem item in items) + { + item.InvokeVerb(); + } + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/map.json b/src/Microsoft.PowerShell.Commands.Management/map.json index 7d4e51d0e..3e0402ac7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/map.json +++ b/src/Microsoft.PowerShell.Commands.Management/map.json @@ -33,6 +33,7 @@ "monad/src/commands/management/GetComputerInfoCommand.cs": "commands/management/GetComputerInfoCommand.cs", "monad/src/singleshell/installer/MshManagementMshSnapin.cs": "singleshell/installer/MshManagementMshSnapin.cs", "monad/src/commands/management/CommandsCommon.cs": "commands/management/CommandsCommon.cs", + "monad/src/commands/management/ControlPanelItemCommand.cs": "commands/management/ControlPanelItemCommand.cs", "monad/src/commands/management/PassThroughContentCommandBase.cs": "commands/management/PassThroughContentCommandBase.cs", "monad/src/commands/management/PassThroughPropertyCommandBase.cs": "commands/management/PassThroughPropertyCommandBase.cs", "monad/src/commands/management/resources/ClearRecycleBinResources.resx": "resources/ClearRecycleBinResources.resx", diff --git a/src/Microsoft.PowerShell.Commands.Management/project.json b/src/Microsoft.PowerShell.Commands.Management/project.json index b44842321..9c0b63c2a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/project.json +++ b/src/Microsoft.PowerShell.Commands.Management/project.json @@ -22,6 +22,7 @@ "compile": { "exclude": [ "commands/management/ClearRecycleBinCommand.cs", + "commands/management/ControlPanelItemCommand.cs", "commands/management/CommitTransactionCommand.cs", "commands/management/Computer.cs", "commands/management/Eventlog.cs", @@ -57,6 +58,13 @@ }, "dependencies": { "Microsoft.WSMan.Management": "1.0.0" + }, + "buildOptions": { + "compile": { + "exclude": [ + "commands/management/ControlPanelItemCommand.cs" + ] + } } } } diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 new file mode 100644 index 000000000..c5db4047d Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 differ diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 new file mode 100644 index 000000000..7f85bb3ae Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 differ diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 new file mode 100644 index 000000000..685e4786a Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 differ diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 new file mode 100644 index 000000000..8a14cedd2 Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 differ diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 new file mode 100644 index 000000000..192149a85 --- /dev/null +++ b/src/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 @@ -0,0 +1,2668 @@ +Import-LocalizedData LocalizedData -FileName Microsoft.PowerShell.ODataUtilsStrings.psd1 + +# Add .NET classes used by the module +Add-Type -TypeDefinition $global:BaseClassDefinitions + +######################################################### +# Generates PowerShell module containing client side +# proxy cmdlets that can be used to interact with an +# OData based server side endpoint. +######################################################### +function ExportODataEndpointProxy +{ + param + ( + [string] $Uri, + [string] $OutputModule, + [string] $MetadataUri, + [PSCredential] $Credential, + [string] $CreateRequestMethod, + [string] $UpdateRequestMethod, + [string] $CmdletAdapter, + [Hashtable] $ResourceNameMapping, + [switch] $Force, + [Hashtable] $CustomData, + [switch] $AllowClobber, + [switch] $AllowUnsecureConnection, + [Hashtable] $Headers, + [string] $ProgressBarStatus, + [System.Management.Automation.PSCmdlet] $PSCmdlet + ) + + # Record of all metadata XML files which have been opened for parsing + # used to avoid parsing the same file twice, if referenced in multiple + # metadata files + $script:processedFiles = @() + + # Record of all referenced and parsed metadata files (including entry point metadata) + $script:GlobalMetadata = New-Object System.Collections.ArrayList + + # The namespace name might have invalid characters or might be conflicting with class names in inheritance scenarios + # We will be normalizing these namespaces and saving them into $normalizedNamespaces, where key is the original namespace and value is normalized namespace + $script:normalizedNamespaces = @{} + + # This information will be used during recursive referenced metadata files loading + $ODataEndpointProxyParameters = [ODataUtils.ODataEndpointProxyParameters] @{ + "MetadataUri" = $MetadataUri; + "Uri" = $Uri; + "Credential" = $Credential; + "OutputModule" = $OutputModule; + "Force" = $Force; + "AllowClobber" = $AllowClobber; + "AllowUnsecureConnection" = $AllowUnsecureConnection; + } + + # Recursively fetch all metadatas (referenced by entry point metadata) + $GlobalMetadata = GetTypeInfo -callerPSCmdlet $pscmdlet -MetadataUri $MetadataUri -ODataEndpointProxyParameters $ODataEndpointProxyParameters -Headers $Headers + # Now that we are done with recursive metadata references parsing we can get rid of this variable + $script:GlobalMetadata = $null + + VerifyMetadata $GlobalMetadata $AllowClobber.IsPresent $PSCmdlet $ProgressBarStatus + + # Get Uri Resource path key format. It can be either 'EmbeddedKey' or 'SeparateKey'. + # If not provided, deault value will be set to 'EmbeddedKey'. + $UriResourcePathKeyFormat = 'EmbeddedKey' + if ($CustomData -and $CustomData.ContainsKey("UriResourcePathKeyFormat")) + { + $UriResourcePathKeyFormat = $CustomData."UriResourcePathKeyFormat" + } + + GenerateClientSideProxyModule $GlobalMetadata $ODataEndpointProxyParameters $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $ResourceNameMapping $CustomData $UriResourcePathKeyFormat $ProgressBarStatus $script:normalizedNamespaces +} + +######################################################### +# GetTypeInfo is a helper method used to get all the types +# from metadata files in a recursive manner +######################################################### +function GetTypeInfo +{ + param + ( + [System.Management.Automation.PSCmdlet] $callerPSCmdlet, + [string] $MetadataUri, + [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, + [Hashtable] $Headers + ) + + if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "callerPSCmdlet", "GetTypeInfo") } + + $metadataSet = New-Object System.Collections.ArrayList + $metadataXML = GetMetaData $MetadataUri $callerPSCmdlet $ODataEndpointProxyParameters.Credential $Headers $ODataEndpointProxyParameters.AllowUnsecureConnection + $script:processedFiles += $MetadataUri + + # parses all referenced metadata XML files recursively + foreach ($reference in $metadataXML.Edmx.Reference) + { + if (-not $script:processedFiles.Contains($reference.Uri)) + { + $tmpMetadataSet = $null + $tmpMetadataSet = GetTypeInfo -callerPSCmdlet $callerPSCmdlet -MetadataUri $reference.Uri -ODataEndpointProxyParameters $ODataEndpointProxyParameters + AddMetadataToMetadataSet -Metadatas $metadataSet -NewMetadata $tmpMetadataSet + } + } + + $metadatas = ParseMetadata -MetadataXML $metadataXML -ODataVersion $metadataXML.Edmx.Version -MetadataUri $MetadataUri -Uri $ODataEndpointProxyParameters.Uri -MetadataSet $script:GlobalMetadata + AddMetadataToMetadataSet -Metadatas $script:GlobalMetadata -NewMetadata $metadatas + AddMetadataToMetadataSet -Metadatas $metadataSet -NewMetadata $metadatas + + return $metadataSet +} + +function AddMetadataToMetadataSet +{ + param + ( + [System.Collections.ArrayList] $Metadatas, + $NewMetadata + ) + + if($NewMetadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "NewMetadata", "AddMetadataToMetadataSet") } + + if ($NewMetadata.GetType().Name -eq 'MetadataV4') + { + $Metadatas.Add($NewMetadata) | Out-Null + } + else + { + $Metadatas.AddRange($NewMetadata) | Out-Null + } +} + +######################################################### +# Normalization of Namespace name will be required in following scenarios: +# 1. Namespace name contains combination of dots and numbers +# 2. Namespace name collides with Class name (EntityType, EntitySet, etc.) +# If normalization is needed, all dots will be replaced with underscores and Ns suffix added +# User will receive warning notifying her about the namespace name change +######################################################### +function NormalizeNamespace +{ + param + ( + [string] $MetadataNamespace, + [string] $MetadataUri, + [Hashtable] $NormalizedNamespaces, + [boolean] $DoesNamespaceConflictsWithClassName + ) + + $doesNamespaceContainsInvalidChars = $false + + # Check if namespace name contains invalid combination if dots and numbers + if ($MetadataNamespace -match '\.[0-9]' -or $MetadataNamespace -match '[0-9]\.') + { + # Normalization needed + $doesNamespaceContainsInvalidChars = $true + } + + # Normalize if needed + if ($doesNamespaceContainsInvalidChars -or $DoesNamespaceConflictsWithClassName) + { + if ($NormalizedNamespaces.ContainsKey($MetadataNamespace)) + { + # It's possible we've already attemted to normalize that namespace. In that case we'll update normalized name. + $NormalizedNamespaces[$MetadataNamespace] = NormalizeNamespaceHelper $NormalizedNamespaces[$MetadataNamespace] $doesNamespaceContainsInvalidChars $DoesNamespaceConflictsWithClassName + } + else + { + $NormalizedNamespaces.Add($MetadataNamespace, (NormalizeNamespaceHelper $MetadataNamespace $doesNamespaceContainsInvalidChars $DoesNamespaceConflictsWithClassName)) + } + } + + # Print warning + if ($doesNamespaceContainsInvalidChars) + { + # Normalization needed + $warningMessage = ($LocalizedData.InValidSchemaNamespaceContainsInvalidChars -f $MetadataUri, $MetadataNamespace, $NormalizedNamespaces[$MetadataNamespace]) + Write-Warning $warningMessage + } + if ($DoesNamespaceConflictsWithClassName) + { + # Collision between namespace name and type name detected (example: namespace TaskService { class Service : Service.BasicService { ... } ... }) + # Normalization needed + $warningMessage = ($LocalizedData.InValidSchemaNamespaceConflictWithClassName -f $MetadataUri, $MetadataNamespace, $NormalizedNamespaces[$MetadataNamespace]) + Write-Warning $warningMessage + } +} + +function NormalizeNamespaceCollisionWithClassName +{ + param + ( + [string] $InheritingType, + [string] $BaseTypeName, + [string] $MetadataUri + ) + + if (![string]::IsNullOrEmpty($BaseTypeName)) + { + $dotNetNamespace = '' + if ($BaseTypeName.LastIndexOf(".") -gt 0) + { + # BaseTypeStr contains Namespace and TypeName. Extract Namespace name. + $dotNetNamespace = $BaseTypeName.SubString(0, $BaseTypeName.LastIndexOf(".")) + } + + if (![string]::IsNullOrEmpty($dotNetNamespace) -and $InheritingType -eq $dotNetNamespace) + { + # Collision between namespace name and type name detected (example: namespace TaskService { class Service : Service.BasicService { ... } ... }) + # Normalization needed + NormalizeNamespace $dotNetNamespace $MetadataUri $script:normalizedNamespaces $true + break + } + } +} + +######################################################### +# This helper method is used by functions, +# writing directly to CDXML files or to .Net namespace/class definitions CompplexTypes file +######################################################### +function GetNamespace +{ + param + ( + [string] $Namespace, + $NormalizedNamespaces, + [boolean] $isClassNameIncluded = $false + ) + + $dotNetNamespace = $Namespace + $dotNetClassName = '' + + # Extract only namespace name + if ($isClassNameIncluded) + { + if ($Namespace.LastIndexOf(".") -gt 0) + { + # For example, from following namespace (Namespace.TypeName) Service.1.0.0.Service we'll extract only namespace name, which is Service.1.0.0 + $dotNetNamespace = $Namespace.SubString(0, $Namespace.LastIndexOf(".")) + $dotNetClassName = $Namespace.SubString($Namespace.LastIndexOf(".") + 1, $Namespace.Length - $Namespace.LastIndexOf(".") - 1) + } + } + + # Check if the namespace has to be normalized. + if ($NormalizedNamespaces.ContainsKey($dotNetNamespace)) + { + $dotNetNamespace = $NormalizedNamespaces.Get_Item($dotNetNamespace) + } + + if (![string]::IsNullOrEmpty($dotNetClassName)) + { + return ($dotNetNamespace + "." + $dotNetClassName) + } + else + { + return $dotNetNamespace + } +} + +function NormalizeNamespaceHelper +{ + param + ( + [string] $Namespace, + [boolean] $DoesNamespaceContainsInvalidChars, + [boolean] $DoesNamespaceConflictsWithClassName + ) + + # For example, following namespace: Service.1.0.0 + # Will change to: Service_1_0_0 + # Ns postfix in Namespace name will allow to diffirintiate between this namespace + # and a colliding type name from different namespace + $updatedNs = $Namespace + if ($DoesNamespaceContainsInvalidChars) + { + $updatedNs = $updatedNs.Replace('.', '_') + } + if ($DoesNamespaceConflictsWithClassName) + { + $updatedNs = $updatedNs + "Ns" + } + + $updatedNs +} + +######################################################### +# Processes EntityTypes (OData V4 schema) from plain text +# xml metadata into our custom structure +######################################################### +function ParseEntityTypes +{ + param + ( + [System.Xml.XmlElement] $SchemaXML, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [hashtable] $EntityAndComplexTypesQueue, + [string] $CustomNamespace, + [AllowEmptyString()] + [string] $Alias + ) + + if($SchemaXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseEntityTypes") } + + foreach ($entityType in $SchemaXML.EntityType) + { + $baseType = $null + + if ($entityType.BaseType -ne $null) + { + # add it to the processing queue + $baseType = GetBaseType $entityType $Metadata $SchemaXML.Namespace $GlobalMetadata + if ($baseType -eq $null) + { + $EntityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='EntityType'; value=$entityType}) + } + + # Check if Namespace has to be normalized because of the collision with the inheriting Class name + NormalizeNamespaceCollisionWithClassName -InheritingType $entityType.Name -BaseTypeName $entityType.BaseType -MetadataUri $Metadata.Uri + } + + [ODataUtils.EntityTypeV4] $newType = ParseMetadataTypeDefinition $entityType $baseType $Metadata $schema.Namespace $Alias $true $entityType.BaseType + $Metadata.EntityTypes += $newType + AddDerivedTypes $newType $entityAndComplexTypesQueue $Metadata $SchemaXML.Namespace + } +} + +######################################################### +# Processes ComplexTypes from plain text xml metadata +# into our custom structure +######################################################### +function ParseComplexTypes +{ + param + ( + [System.Xml.XmlElement] $SchemaXML, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [hashtable] $EntityAndComplexTypesQueue, + [string] $CustomNamespace, + [AllowEmptyString()] + [string] $Alias + ) + + if($SchemaXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseComplexTypes") } + + foreach ($complexType in $SchemaXML.ComplexType) + { + $baseType = $null + + if ($complexType.BaseType -ne $null) + { + # add it to the processing queue + $baseType = GetBaseType $complexType $metadata $SchemaXML.Namespace $GlobalMetadata + if ($baseType -eq $null -and $entityAndComplexTypesQueue -ne $null -and $entityAndComplexTypesQueue.ContainsKey($complexType.BaseType)) + { + $entityAndComplexTypesQueue[$complexType.BaseType] += @(@{type='ComplexType'; value=$complexType}) + continue + } + + # Check if Namespace has to be normalized because of the collision with the inheriting Class name + NormalizeNamespaceCollisionWithClassName -InheritingType $complexType.Name -BaseTypeName $complexType.BaseType -MetadataUri $Metadata.Uri + } + + [ODataUtils.EntityTypeV4] $newType = ParseMetadataTypeDefinition $complexType $baseType $Metadata $schema.Namespace -Alias $Alias $false $complexType.BaseType + $Metadata.ComplexTypes += $newType + AddDerivedTypes $newType $entityAndComplexTypesQueue $metadata $schema.Namespace + } +} + +######################################################### +# Processes TypeDefinition from plain text xml metadata +# into our custom structure +######################################################### +function ParseTypeDefinitions +{ + param + ( + [System.Xml.XmlElement] $SchemaXML, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [string] $CustomNamespace, + [AllowEmptyString()] + [string] $Alias + ) + + if($SchemaXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseTypeDefinitions") } + + + foreach ($typeDefinition in $SchemaXML.TypeDefinition) + { + $newType = [ODataUtils.EntityTypeV4] @{ + "Namespace" = $Metadata.Namespace; + "Alias" = $Metadata.Alias; + "Name" = $typeDefinition.Name; + "BaseTypeStr" = $typeDefinition.UnderlyingType; + } + $Metadata.TypeDefinitions += $newType + } +} + +######################################################### +# Processes EnumTypes from plain text xml metadata +# into our custom structure +######################################################### +function ParseEnumTypes +{ + param + ( + [System.Xml.XmlElement] $SchemaXML, + [ODataUtils.MetadataV4] $Metadata + ) + + if($SchemaXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseEnumTypes") } + + foreach ($enum in $SchemaXML.EnumType) + { + $newEnumType = [ODataUtils.EnumType] @{ + "Namespace" = $Metadata.Namespace; + "Alias" = $Metadata.Alias; + "Name" = $enum.Name; + "UnderlyingType" = $enum.UnderlyingType; + "IsFlags" = $enum.IsFlags; + "Members" = @() + } + + if (!$newEnumType.UnderlyingType) + { + # If no type specified set the default type which is Edm.Int32 + $newEnumType.UnderlyingType = "Edm.Int32" + } + + if ($newEnumType.IsFlags -eq $null) + { + # If no value is specified for IsFlags, its value defaults to false. + $newEnumType.IsFlags = $false + } + + $enumValue = 0 + $currentEnumValue = 0 + + # Now parse EnumType elements + foreach ($element in $enum.Member) + { + + if ($element.Value -eq "" -and $newEnumType.IsFlags -eq $true) + { + # When IsFlags set to true each edm:Member element MUST specify a non-negative integer Value in the value attribute + $errorMessage = ($LocalizedData.InValidMetadata) + $detailedErrorMessage = "When IsFlags set to true each edm:Member element MUST specify a non-negative integer Value in the value attribute in " + $newEnumType.Name + " EnumType" + $exception = [System.InvalidOperationException]::new($errorMessage, $detailedErrorMessage) + $errorRecord = CreateErrorRecordHelper "InValidMetadata" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $detailedErrorMessage nu + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + elseif (($element.Value -eq $null) -or ($element.Value.GetType().Name -eq "Int32" -and $element.Value -eq "")) + { + # If no values are specified, the members are assigned consecutive integer values in the order of their appearance, + # starting with zero for the first member. + $currentEnumValue = $enumValue + } + else + { + $currentEnumValue = $element.Value + } + + $tmp = [ODataUtils.EnumMember] @{ + "Name" = $element.Name; + "Value" = $currentEnumValue; + } + + $newEnumType.Members += $tmp + $enumValue++ + } + + $Metadata.EnumTypes += $newEnumType + } +} + +######################################################### +# Processes SingletonTypes from plain text xml metadata +# into our custom structure +######################################################### +function ParseSingletonTypes +{ + param + ( + [System.Xml.XmlElement] $SchemaEntityContainerXML, + [ODataUtils.MetadataV4] $Metadata + ) + + if($SchemaEntityContainerXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaEntityContainerXML", "ParseSingletonTypes") } + + foreach ($singleton in $SchemaEntityContainerXML.Singleton) + { + $navigationPropertyBindings = @() + + foreach ($navigationPropertyBinding in $singleton.NavigationPropertyBinding) + { + $tmp = [ODataUtils.NavigationPropertyBinding] @{ + "Path" = $navigationPropertyBinding.Path; + "Target" = $navigationPropertyBinding.Target; + } + + $navigationPropertyBindings += $tmp + } + + $newSingletonType = [ODataUtils.SingletonType] @{ + "Namespace" = $Metadata.Namespace; + "Alias" = $Metadata.Alias; + "Name" = $singleton.Name; + "Type" = $singleton.Type; + "NavigationPropertyBindings" = $navigationPropertyBindings; + } + + $Metadata.SingletonTypes += $newSingletonType + } +} + +######################################################### +# Processes EntitySets from plain text xml metadata +# into our custom structure +######################################################### +function ParseEntitySets +{ + param + ( + [System.Xml.XmlElement] $SchemaEntityContainerXML, + [ODataUtils.MetadataV4] $Metadata, + [string] $Namespace, + [AllowEmptyString()] + [string] $Alias + ) + + if($SchemaEntityContainerXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaEntityContainerXML", "ParseEntitySets") } + + $entityTypeToEntitySetMapping = @{}; + foreach ($entitySet in $SchemaEntityContainerXML.EntitySet) + { + $entityType = $metadata.EntityTypes | Where-Object { $_.Name -eq $entitySet.EntityType.Split('.')[-1] } + $entityTypeName = $entityType.Name + + if($entityTypeToEntitySetMapping.ContainsKey($entityTypeName)) + { + $existingEntitySetName = $entityTypeToEntitySetMapping[$entityTypeName] + throw ($LocalizedData.EntityNameConflictError -f $entityTypeName, $existingEntitySetName, $entitySet.Name, $entityTypeName ) + } + else + { + $entityTypeToEntitySetMapping.Add($entityTypeName, $entitySet.Name) + } + + $newEntitySet = [ODataUtils.EntitySetV4] @{ + "Namespace" = $Namespace; + "Alias" = $Alias; + "Name" = $entitySet.Name; + "Type" = $entityType; + } + + $Metadata.EntitySets += $newEntitySet + } +} + +######################################################### +# Processes Actions from plain text xml metadata +# into our custom structure +######################################################### +function ParseActions +{ + param + ( + [System.Object[]] $SchemaActionsXML, + [ODataUtils.MetadataV4] $Metadata + ) + + if($SchemaActionsXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaActionsXML", "ParseActions") } + + foreach ($action in $SchemaActionsXML) + { + # HttpMethod is only used for legacy Service Operations + if ($action.HttpMethod -eq $null) + { + $newAction = [ODataUtils.ActionV4] @{ + "Namespace" = $Metadata.Namespace; + "Alias" = $Metadata.Alias; + "Name" = $action.Name; + "Action" = $Metadata.Namespace + '.' + $action.Name; + } + + # Actions are always SideEffecting, otherwise it's an OData function + foreach ($parameter in $action.Parameter) + { + if ($parameter.Nullable -ne $null) + { + $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable); + } + else + { + $parameterIsNullable = $true + } + + $newParameter = [ODataUtils.TypeProperty] @{ + "Name" = $parameter.Name; + "TypeName" = $parameter.Type; + "IsNullable" = $parameterIsNullable; + } + + $newAction.Parameters += $newParameter + } + + if ($action.EntitySet -ne $null) + { + $newAction.EntitySet = $metadata.EntitySets | Where-Object { $_.Name -eq $action.EntitySet } + } + + $Metadata.Actions += $newAction + } + } +} + +######################################################### +# Processes Functions from plain text xml metadata +# into our custom structure +######################################################### +function ParseFunctions +{ + param + ( + [System.Object[]] $SchemaFunctionsXML, + [ODataUtils.MetadataV4] $Metadata + ) + + if($SchemaFunctionsXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "SchemaFunctionsXML", "ParseFunctions") } + + foreach ($function in $SchemaFunctionsXML) + { + # HttpMethod is only used for legacy Service Operations + if ($function.HttpMethod -eq $null) + { + $newFunction = [ODataUtils.FunctionV4] @{ + "Namespace" = $Metadata.Namespace; + "Alias" = $Metadata.Alias; + "Name" = $function.Name; + "Function" = $Metadata.Namespace + '.' + $function.Name; + "EntitySet" = $function.EntitySetPath; + "ReturnType" = $function.ReturnType; + } + + # Future TODO - consider removing this hack once all the service we run against fix this issue + # Hack - sometimes service does not return ReturnType, however this information can be found in InnerXml + if ($newFunction.ReturnType -eq '' -or $newFunction.ReturnType -eq 'System.Xml.XmlElement') + { + try + { + [xml] $innerXML = '' + $function.InnerXml + '' + $newFunction.Returntype = $innerXML.Params.ReturnType.Type + } + catch + { + # Do nothing + } + } + + # Keep only EntityType name + $newFunction.ReturnType = $newFunction.ReturnType.Replace('Collection(', '') + $newFunction.ReturnType = $newFunction.ReturnType.Replace(')', '') + + # Actions are always SideEffecting, otherwise it's an OData function + foreach ($parameter in $function.Parameter) + { + if ($parameter.Nullable -ne $null) + { + $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable); + } + + $newParameter = [ODataUtils.Parameter] @{ + "Name" = $parameter.Name; + "Type" = $parameter.Type; + "Nullable" = $parameterIsNullable; + } + + $newFunction.Parameters += $newParameter + } + + $Metadata.Functions += $newFunction + } + } +} + +######################################################### +# Processes plain text xml metadata (OData V4 schema version) into our custom structure +# MetadataSet contains all parsed so far referenced Metadatas (for base class lookup) +######################################################### +function ParseMetadata +{ + param + ( + [xml] $MetadataXML, + [string] $ODataVersion, + [string] $MetadataUri, + [string] $Uri, + [System.Collections.ArrayList] $MetadataSet + ) + + if($MetadataXML -eq $null) { throw ($LocalizedData.ArguementNullError -f "MetadataXML", "ParseMetadata") } + + # This is a processing queue for those types that require base types that haven't been defined yet + $entityAndComplexTypesQueue = @{} + [System.Collections.ArrayList] $metadatas = [System.Collections.ArrayList]::new() + + foreach ($schema in $MetadataXML.Edmx.DataServices.Schema) + { + if ($schema -eq $null) + { + Write-Error $LocalizedData.EmptySchema + continue + } + + [ODataUtils.MetadataV4] $metadata = [ODataUtils.MetadataV4]::new() + $metadata.ODataVersion = $ODataVersion + $metadata.MetadataUri = $MetadataUri + $metadata.Uri = $Uri + $metadata.Namespace = $schema.Namespace + $metadata.Alias = $schema.Alias + + ParseEntityTypes -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -EntityAndComplexTypesQueue $entityAndComplexTypesQueue -CustomNamespace $CustomNamespace -Alias $metadata.Alias + ParseComplexTypes -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -EntityAndComplexTypesQueue $entityAndComplexTypesQueue -CustomNamespace $CustomNamespace -Alias $metadata.Alias + ParseTypeDefinitions -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -CustomNamespace $CustomNamespace -Alias $metadata.Alias + ParseEnumTypes -SchemaXML $schema -metadata $metadata + + foreach ($entityContainer in $schema.EntityContainer) + { + if ($entityContainer.IsDefaultEntityContainer) + { + $metadata.DefaultEntityContainerName = $entityContainer.Name + } + + ParseSingletonTypes -SchemaEntityContainerXML $entityContainer -Metadata $metadata + ParseEntitySets -SchemaEntityContainerXML $entityContainer -Metadata $metadata -Namespace $schema.Namespace -Alias $schema.Alias + } + + if ($schema.Action) + { + ParseActions -SchemaActionsXML $schema.Action -Metadata $metadata + } + + if ($schema.Function) + { + ParseFunctions -SchemaFunctionsXML $schema.Function -Metadata $metadata + } + + # In this call we check if the Namespace or Alias have to be normalized because it has invalid combination of dots and numbers. + # Note: In ParseEntityTypes and ParseComplexTypes we check for scenario where namespace/alias collide with inheriting class name. + NormalizeNamespace $metadata.Namespace $metadata.Uri $script:normalizedNamespaces $false + NormalizeNamespace $metadata.Alias $metadata.Uri $script:normalizedNamespaces $false + + $metadatas.Add($metadata) | Out-Null + } + + $metadatas +} + +######################################################### +# Verifies processed metadata for correctness +######################################################### +function VerifyMetadata +{ + param + ( + [System.Collections.ArrayList] $metadataSet, + [boolean] $allowClobber, + $callerPSCmdlet, + [string] $progressBarStatus + ) + + if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "VerifyMetaData") } + if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "VerifyMetaData") } + + Write-Verbose $LocalizedData.VerboseVerifyingMetadata + + $reservedProperties = @("Filter", "OrderBy", "Skip", "Top", "ConnectionUri", "CertificateThumbPrint", "Credential") + $validEntitySets = @() + $sessionCommands = Get-Command -All + + + foreach ($metadata in $metadataSet) + { + foreach ($entitySet in $metadata.EntitySets) + { + if ($entitySet.Type -eq $null) + { + $errorMessage = ($LocalizedData.EntitySetUndefinedType -f $metadata.MetadataUri, $entitySet.Name) + $exception = [System.InvalidOperationException]::new($errorMessage) + $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metadata.MetadataUri + $callerPSCmdlet.ThrowTerminatingError($errorRecord) + } + + $hasConflictingProperty = $false + $hasConflictingCommand = $false + $entityAndNavigationProperties = $entitySet.Type.EntityProperties + $entitySet.Type.NavigationProperties + foreach($entityProperty in $entityAndNavigationProperties) + { + if($reservedProperties.Contains($entityProperty.Name)) + { + $hasConflictingProperty = $true + if(!$allowClobber) + { + # Write Error message and skip current Entity Set. + $errorMessage = ($LocalizedData.SkipEntitySetProxyCreation -f $entitySet.Name, $entitySet.Type.Name, $entityProperty.Name) + $exception = [System.InvalidOperationException]::new($errorMessage) + $errorRecord = CreateErrorRecordHelper "ODataEndpointDefaultPropertyCollision" $null ([System.Management.Automation.ErrorCategory]::InvalidOperation) $exception $metadata.MetadataUri + $callerPSCmdlet.WriteError($errorRecord) + } + else + { + $warningMessage = ($LocalizedData.EntitySetProxyCreationWithWarning -f $entitySet.Name, $entityProperty.Name, $entitySet.Type.Name) + $callerPSCmdlet.WriteWarning($warningMessage) + } + } + } + + foreach($currentCommand in $sessionCommands) + { + if(($null -ne $currentCommand.Noun -and $currentCommand.Noun -eq $entitySet.Name) -and + ($currentCommand.Verb -eq "Get" -or + $currentCommand.Verb -eq "Set" -or + $currentCommand.Verb -eq "New" -or + $currentCommand.Verb -eq "Remove")) + { + $hasConflictingCommand = $true + VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` + $LocalizedData.EntitySetConflictCommandCreationWithWarning ` + $entitySet.Name $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet + } + } + + foreach($currentAction in $metadata.Actions) + { + $actionCommand = "Invoke-" + "$($entitySet.Name)$($currentAction.Verb)" + + foreach($currentCommand in $sessionCommands) + { + if($actionCommand -eq $currentCommand.Name) + { + $hasConflictingCommand = $true + VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` + $LocalizedData.EntitySetConflictCommandCreationWithWarning $entitySet.Name ` + $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet + } + } + } + + if(!($hasConflictingProperty -or $hasConflictingCommand)-or $allowClobber) + { + $validEntitySets += $entitySet + } + } + + $metadata.EntitySets = $validEntitySets + + $validServiceActions = @() + $hasConflictingServiceActionCommand + foreach($currentAction in $metadata.Actions) + { + $serviceActionCommand = "Invoke-" + "$($currentAction.Verb)" + + foreach($currentCommand in $sessionCommands) + { + if($serviceActionCommand -eq $currentCommand.Name) + { + $hasConflictingServiceActionCommand = $true + VerifyMetadataHelper $LocalizedData.SkipConflictServiceActionCommandCreation ` + $LocalizedData.ConflictServiceActionCommandCreationWithWarning $entitySet.Name ` + $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet + } + } + + if(!$hasConflictingServiceActionCommand -or $allowClobber) + { + $validServiceActions += $currentAction + } + } + + $metadata.Actions = $validServiceActions + } + + # Update Progress bar. + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 5 20 1 1 +} + +######################################################### +# Takes xml definition of a class from metadata document, +# plus existing metadata structure and finds its base class +######################################################### +function GetBaseType +{ + param + ( + [System.Xml.XmlElement] $MetadataEntityDefinition, + [ODataUtils.MetadataV4] $Metadata, + [string] $Namespace, + [System.Collections.ArrayList] $GlobalMetadata + ) + + if ($metadataEntityDefinition -ne $null -and + $metaData -ne $null -and + $MetadataEntityDefinition.BaseType -ne $null) + { + $baseType = $Metadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType } + if ($baseType -eq $null) + { + $baseType = $Metadata.ComplexTypes | Where { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType } + } + + if ($baseType -eq $null) + { + # Look in other metadatas, since the class can be defined in referenced metadata + foreach ($referencedMetadata in $GlobalMetadata) + { + if (($baseType = $referencedMetadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType }) -ne $null -or + ($baseType = $referencedMetadata.ComplexTypes | Where { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType }) -ne $null) + { + # Found base class + break + } + } + } + } + + if ($baseType -ne $null) + { + $baseType[0] + } +} + +######################################################### +# Takes base class name and global metadata structure +# and finds its base class +######################################################### +function GetBaseTypeByName +{ + param + ( + [String] $BaseTypeStr, + [System.Collections.ArrayList] $GlobalMetadata + ) + + if ($BaseTypeStr -ne $null) + { + + # Look for base class definition in all referenced metadatas (including entry point) + foreach ($referencedMetadata in $GlobalMetadata) + { + if (($baseType = $referencedMetadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $BaseTypeStr -or $_.Alias + "." + $_.Name -eq $BaseTypeStr }) -ne $null -or + ($baseType = $referencedMetadata.ComplexTypes | Where { $_.Namespace + "." + $_.Name -eq $BaseTypeStr -or $_.Alias + "." + $_.Name -eq $BaseTypeStr }) -ne $null) + { + # Found base class + break + } + } + } + + if ($baseType -ne $null) + { + $baseType[0] + } + else + { + $null + } +} + +######################################################### +# Processes derived types of a newly added type, +# that were previously waiting in the queue +######################################################### +function AddDerivedTypes { + param( + [ODataUtils.EntityTypeV4] $baseType, + $entityAndComplexTypesQueue, + [ODataUtils.MetadataV4] $metadata, + [string] $namespace + ) + + if($baseType -eq $null) { throw ($LocalizedData.ArguementNullError -f "BaseType", "AddDerivedTypes") } + if($entityAndComplexTypesQueue -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntityAndComplexTypesQueue", "AddDerivedTypes") } + if($namespace -eq $null) { throw ($LocalizedData.ArguementNullError -f "Namespace", "AddDerivedTypes") } + + $baseTypeFullName = $baseType.Namespace + '.' + $baseType.Name + $baseTypeShortName = $baseType.Alias + '.' + $baseType.Name + + if ($entityAndComplexTypesQueue.ContainsKey($baseTypeFullName) -or $entityAndComplexTypesQueue.ContainsKey($baseTypeShortName)) + { + $types = $entityAndComplexTypesQueue[$baseTypeFullName] + $entityAndComplexTypesQueue[$baseTypeShortName] + + foreach ($type in $types) + { + if ($type.type -eq 'EntityType') + { + $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metadata $namespace $true + $metadata.EntityTypes += $newType + } + else + { + $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metadata $namespace $false + $metadata.ComplexTypes += $newType + } + + AddDerivedTypes $newType $entityAndComplexTypesQueue $metadata $namespace + } + } +} + +######################################################### +# Parses types definitions element of metadata xml +######################################################### +function ParseMetadataTypeDefinitionHelper +{ + param + ( + [System.Xml.XmlElement] $metadataEntityDefinition, + [ODataUtils.EntityTypeV4] $baseType, + [string] $baseTypeStr, + [ODataUtils.MetadataV4] $metadata, + [string] $namespace, + [AllowEmptyString()] + [string] $alias, + [bool] $isEntity + ) + + if($metadataEntityDefinition -eq $null) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") } + if($namespace -eq $null) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") } + + [ODataUtils.EntityTypeV4] $newEntityType = CreateNewEntityType -metadataEntityDefinition $metadataEntityDefinition -baseType $baseType -baseTypeStr $baseTypeStr -namespace $namespace -alias $alias -isEntity $isEntity + + if ($baseType -ne $null) + { + # Add properties inherited from BaseType + ParseMetadataBaseTypeDefinitionHelper $newEntityType $baseType + } + + # properties defined on EntityType + $newEntityType.EntityProperties += $metadataEntityDefinition.Property | % { + if ($_ -ne $null) + { + if ($_.Nullable -ne $null) + { + $newPropertyIsNullable = [System.Convert]::ToBoolean($_.Nullable) + } + else + { + $newPropertyIsNullable = $true + } + + [ODataUtils.TypeProperty] @{ + "Name" = $_.Name; + "TypeName" = $_.Type; + "IsNullable" = $newPropertyIsNullable; + } + } + } + + # odataId property will be inherited from base type, if it exists. + # Otherwise, it should be added to current type + if ($baseType -eq $null) + { + # @odata.Id property (renamed to odataId) is required for dynamic Uri creation + # This property is only available when user executes auto-generated cmdlet with -AllowAdditionalData, + # but ODataAdapter needs it to construct Uri to access navigation properties. + # Thus, we need to fetch this info for scenario when -AllowAdditionalData isn't used. + $newEntityType.EntityProperties += [ODataUtils.TypeProperty] @{ + "Name" = "odataId"; + "TypeName" = "Edm.String"; + "IsNullable" = $True; + } + } + + # Property name can't be identical to entity type name. + # If such property exists, "Property" suffix will be added to its name. + foreach ($property in $newEntityType.EntityProperties) + { + if ($property.Name -eq $newEntityType.Name) + { + $property.Name += "Property" + } + } + + if ($metadataEntityDefinition -ne $null -and $metadataEntityDefinition.Key -ne $null) + { + foreach ($entityTypeKey in $metadataEntityDefinition.Key.PropertyRef) + { + ($newEntityType.EntityProperties | Where-Object { $_.Name -eq $entityTypeKey.Name }).IsKey = $true + } + } + + $newEntityType +} + +######################################################### +# Add base class entity and navigation properties to inheriting class +######################################################### +function ParseMetadataBaseTypeDefinitionHelper +{ + param + ( + [ODataUtils.EntityTypeV4] $EntityType, + [ODataUtils.EntityTypeV4] $BaseType + ) + + if ($EntityType -ne $null -and $BaseType -ne $null) + { + # Add properties inherited from BaseType + $EntityType.EntityProperties += $BaseType.EntityProperties + $EntityType.NavigationProperties += $BaseType.NavigationProperties + } +} + +######################################################### +# Create new EntityType object +######################################################### +function CreateNewEntityType +{ + param + ( + [System.Xml.XmlElement] $metadataEntityDefinition, + [ODataUtils.EntityTypeV4] $baseType, + [string] $baseTypeStr, + [string] $namespace, + [AllowEmptyString()] + [string] $alias, + [bool] $isEntity + ) + $newEntityType = [ODataUtils.EntityTypeV4] @{ + "Namespace" = $namespace; + "Alias" = $alias; + "Name" = $metadataEntityDefinition.Name; + "IsEntity" = $isEntity; + "BaseType" = $baseType; + "BaseTypeStr" = $baseTypeStr; + } + + $newEntityType +} + +######################################################### +# Parses navigation properties from metadata xml +######################################################### +function ParseMetadataTypeDefinitionNavigationProperties +{ + param + ( + [System.Xml.XmlElement] $metadataEntityDefinition, + [ODataUtils.EntityTypeV4] $entityType + ) + + # navigation properties defined on EntityType + $newEntityType.NavigationProperties = @{} + $newEntityType.NavigationProperties.Clear() + + foreach ($navigationProperty in $metadataEntityDefinition.NavigationProperty) + { + $tmp = [ODataUtils.NavigationPropertyV4] @{ + "Name" = $navigationProperty.Name; + "Type" = $navigationProperty.Type; + "Nullable" = $navigationProperty.Nullable; + "Partner" = $navigationProperty.Partner; + "ContainsTarget" = $navigationProperty.ContainsTarget; + "OnDelete" = $navigationProperty.OnDelete; + } + + $referentialConstraints = @{} + foreach ($constraint in $navigationProperty.ReferentialConstraints) + { + $tmp = [ODataUtils.ReferencedConstraint] @{ + "Property" = $constraint.Property; + "ReferencedProperty" = $constraint.ReferencedProperty; + } + } + + $newEntityType.NavigationProperties += $tmp + } +} + +######################################################### +# Parses types definitions element of metadata xml for OData V4 schema +######################################################### +function ParseMetadataTypeDefinition +{ + param + ( + [System.Xml.XmlElement] $metadataEntityDefinition, + [ODataUtils.EntityTypeV4] $baseType, + [ODataUtils.MetadataV4] $metadata, + [string] $namespace, + [AllowEmptyString()] + [string] $alias, + [bool] $isEntity, + [string] $baseTypeStr + ) + + if($metadataEntityDefinition -eq $null) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") } + if($namespace -eq $null) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") } + + [ODataUtils.EntityTypeV4] $newEntityType = ParseMetadataTypeDefinitionHelper -metadataEntityDefinition $metadataEntityDefinition -baseType $baseType -baseTypeStr $baseTypeStr -metadata $metadata -namespace $namespace -alias $alias -isEntity $isEntity + ParseMetadataTypeDefinitionNavigationProperties -metadataEntityDefinition $metadataEntityDefinition -entityType $newEntityType + + $newEntityType +} + +######################################################### +# Create psd1 and cdxml files required to auto-generate +# cmdlets for given service. +######################################################### +function GenerateClientSideProxyModule +{ + param + ( + [System.Collections.ArrayList] $GlobalMetadata, + [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, + [string] $OutputModule, + [string] $CreateRequestMethod, + [string] $UpdateRequestMethod, + [string] $CmdletAdapter, + [Hashtable] $resourceNameMappings, + [Hashtable] $CustomData, + [string] $UriResourcePathKeyFormat, + [string] $progressBarStatus, + $NormalizedNamespaces + ) + + if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") } + + Write-Verbose ($LocalizedData.VerboseSavingModule -f $OutputModule) + + # Save ComplexTypes for all metadata schemas in single file + $typeDefinationFileName = "ComplexTypeDefinitions.psm1" + $complexTypeMapping = GenerateComplexTypeDefinition $GlobalMetadata $OutputModule $typeDefinationFileName $NormalizedNamespaces + + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 20 20 1 1 + + $actions = @() + $functions = @() + + $currentEntryCount = 0 + foreach ($Metadata in $GlobalMetadata) + { + foreach ($entitySet in $Metadata.EntitySets) + { + $currentEntryCount += 1 + SaveCDXML $entitySet $Metadata $GlobalMetadata $Metadata.Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $resourceNameMappings $CustomData $complexTypeMapping $UriResourcePathKeyFormat $NormalizedNamespaces + + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $Metadata.EntitySets.Count $currentEntryCount + } + + $currentEntryCount = 0 + foreach ($singleton in $Metadata.SingletonTypes) + { + $currentEntryCount += 1 + SaveCDXMLSingletonCmdlets $singleton $Metadata $GlobalMetadata $Metadata.Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $resourceNameMappings $CustomData $complexTypeMapping $NormalizedNamespaces + + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $Metadata.Singletons.Count $currentEntryCount + } + + $actions += $Metadata.Actions | Where-Object { $_.EntitySet -eq '' -or $_.EntitySet -eq $null } + $functions += $Metadata.Functions | Where-Object { $_.EntitySet -eq '' -or $_.EntitySet -eq $null } + } + + if ($actions.Count -gt 0 -or $functions.Count -gt 0) + { + # Save Service Actions for all metadata schemas in single file + SaveServiceActionsCDXML $GlobalMetadata $ODataEndpointProxyParameters "$OutputModule\ServiceActions.cdxml" $complexTypeMapping $progressBarStatus $CmdletAdapter + } + + $moduleDirInfo = [System.IO.DirectoryInfo]::new($OutputModule) + $moduleManifestName = $moduleDirInfo.Name + ".psd1" + + if ($actions.Count -gt 0 -or $functions.Count -gt 0) + { + $additionalModules = @($typeDefinationFileName, 'ServiceActions.cdxml') + } + else + { + $additionalModules = @($typeDefinationFileName) + } + + GenerateModuleManifest $GlobalMetadata $OutputModule\$moduleManifestName $additionalModules $resourceNameMappings $progressBarStatus +} + +######################################################### +# Generates CDXML module for a specific OData EntitySet +######################################################### +function SaveCDXML +{ + param + ( + [ODataUtils.EntitySetV4] $EntitySet, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [string] $Uri, + [string] $OutputModule, + [string] $CreateRequestMethod, + [string] $UpdateRequestMethod, + [string] $CmdletAdapter, + [Hashtable] $resourceNameMappings, + [Hashtable] $CustomData, + [Hashtable] $compexTypeMapping, + [string] $UriResourcePathKeyFormat, + $normalizedNamespaces + ) + + if($EntitySet -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntitySet", "GenerateClientSideProxyModule") } + if($Metadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") } + + $entitySetName = $EntitySet.Name + if(($null -ne $resourceNameMappings) -and + $resourceNameMappings.ContainsKey($entitySetName)) + { + $entitySetName = $resourceNameMappings[$entitySetName] + } + else + { + $entitySetName = $EntitySet.Type.Name + } + + $Path = "$OutputModule\$entitySetName.cdxml" + + $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) + + if ($xmlWriter -eq $null) + { + throw ($LocalizedData.XmlWriterInitializationError -f $EntitySet.Name) + } + + $xmlWriter = SaveCDXMLHeader $xmlWriter $Uri $EntitySet.Name $entitySetName $CmdletAdapter + + # Get the keys + $keys = $EntitySet.Type.EntityProperties | Where-Object { $_.IsKey } + + $navigationProperties = $EntitySet.Type.NavigationProperties + + SaveCDXMLInstanceCmdlets $xmlWriter $Metadata $GlobalMetadata $EntitySet.Type $keys $navigationProperties $CmdletAdapter $compexTypeMapping $false + + $nonKeyProperties = $EntitySet.Type.EntityProperties | ? { -not $_.isKey } + $nullableProperties = $nonKeyProperties | ? { $_.isNullable } + $nonNullableProperties = $nonKeyProperties | ? { -not $_.isNullable } + + $xmlWriter.WriteStartElement('StaticCmdlets') + + $keyProperties = $keys + + SaveCDXMLNewCmdlet $xmlWriter $Metadata $GlobalMetadata $keyProperties $nonNullableProperties $nullableProperties $navigationProperties $CmdletAdapter $compexTypeMapping + + GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $compexTypeMapping + + SaveCDXMLRemoveCmdlet $xmlWriter $Metadata $GlobalMetadata $keyProperties $navigationProperties $CmdletAdapter $compexTypeMapping + + $entityActions = $Metadata.Actions | Where-Object { ($_.EntitySet.Namespace -eq $EntitySet.Namespace) -and ($_.EntitySet.Name -eq $EntitySet.Name) } + + if ($entityActions.Length -gt 0) + { + foreach($action in $entityActions) + { + $xmlWriter = SaveCDXMLAction $xmlWriter $Metadata $action $EntitySet.Name $true $keys $complexTypeMapping + } + } + + $entityFunctions = $Metadata.Functions | Where-Object { ($_.EntitySet.Namespace -eq $EntitySet.Namespace) -and ($_.EntitySet.Name -eq $EntitySet.Name) } + + if ($entityFunctions.Length -gt 0) + { + foreach($function in $entityFunctions) + { + $xmlWriter = SaveCDXMLFunction $xmlWriter $Metadata $function $EntitySet.Name $true $keys $complexTypeMapping + } + } + + $xmlWriter.WriteEndElement() + + $normalizedDotNetNamespace = GetNamespace $EntitySet.Type.Namespace $normalizedNamespaces + $normalizedDotNetAlias = GetNamespace $EntitySet.Alias $normalizedNamespaces + $normalizedDotNetEntitySetNamespace = $normalizedDotNetNamespace + + $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') + + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'EntityTypeName') + $xmlWriter.WriteString("$($normalizedDotNetNamespace).$($EntitySet.Type.Name)") + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'EntityTypeAliasName') + if (!$EntitySet.Alias) + { + $xmlWriter.WriteString("") + } + else + { + $xmlWriter.WriteString("$($normalizedDotNetAlias).$($EntitySet.Type.Name)") + } + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'EntitySetName') + $xmlWriter.WriteString("$($normalizedDotNetEntitySetNamespace).$($EntitySet.Name)") + $xmlWriter.WriteEndElement() + + # Add URI resource path format (webservice.svc/ResourceName/ResourceId vs webservice.svc/ResourceName(QueryKeyName=ResourceId)) + if ($UriResourcePathKeyFormat -ne $null -and $UriResourcePathKeyFormat -ne '') + { + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'UriResourcePathKeyFormat') + $xmlWriter.WriteString("$UriResourcePathKeyFormat") + $xmlWriter.WriteEndElement() + } + + # Add information about navigation properties and their types + # Used in scenario where user requests navigation property in -Select query + foreach ($navProperty in $navigationProperties) + { + if ($navProperty) + { + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', $navProperty.Name + 'NavigationProperty') + $xmlWriter.WriteString($navProperty.Type) + $xmlWriter.WriteEndElement() + } + } + + # Add CreateRequestMethod and UpdateRequestMethod to privateData + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod') + $xmlWriter.WriteString("$CreateRequestMethod") + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod') + $xmlWriter.WriteString("$UpdateRequestMethod") + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteEndElement() + + SaveCDXMLFooter $xmlWriter + + Write-Verbose ($LocalizedData.VerboseSavedCDXML -f $($entitySetName), $Path) +} + +######################################################### +# Save Singleton Cmdlets to CDXML +######################################################### +function SaveCDXMLSingletonCmdlets +{ + param + ( + [ODataUtils.SingletonType] $Singleton, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [string] $Uri, + [string] $OutputModule, + [string] $CreateRequestMethod, + [string] $UpdateRequestMethod, + [string] $CmdletAdapter, + [Hashtable] $resourceNameMappings, + [Hashtable] $CustomData, + [Hashtable] $compexTypeMapping, + $normalizedNamespaces + ) + + if($Singleton -eq $null) { throw ($LocalizedData.ArguementNullError -f "Singleton", "SaveCDXMLSingletonCmdlets") } + if($Metadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLSingletonCmdlets") } + + $singletonName = $singleton.Name + $singletonType = $singleton.Type + + $Path = "$OutputModule\$singletonName" + "Singleton" + ".cdxml" + + $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) + + if ($xmlWriter -eq $null) + { + throw ($LocalizedData.XmlWriterInitializationError -f $singletonName) + } + + # Get associated EntityType + $associatedEntityType = $Metadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $singletonType -or $_.Alias + "." + $_.Name -eq $singletonType} + + if ($associatedEntityType -eq $null) + { + # Look in other metadatas, since the class can be defined in referenced metadata + foreach ($referencedMetadata in $GlobalMetadata) + { + if (($associatedEntityType = $referencedMetadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $singletonType -or $_.Alias + "." + $_.Name -eq $singletonType }) -ne $null) + { + # Found associated class + break + } + } + } + + if ($associatedEntityType -ne $null) + { + $xmlWriter = SaveCDXMLHeader $xmlWriter $Uri $singletonName $singletonName $CmdletAdapter + + if ($associatedEntityType.BaseType -eq $null -and $associatedEntityType.BaseTypeStr -ne $null -and $associatedEntityType.BaseTypeStr -ne '') + { + $associatedEntitybaseType = GetBaseTypeByName $associatedEntityType.BaseTypeStr $GlobalMetadata + + # Make another pass on base class to make sure its properties were added to associated entity type + ParseMetadataBaseTypeDefinitionHelper $associatedEntityType $associatedEntitybaseType + } + + # Get the keys depending on whether the url contains variables or not + $keys = $associatedEntityType.EntityProperties | Where-Object { $_.IsKey } + + $navigationProperties = $associatedEntityType.NavigationProperties + + SaveCDXMLInstanceCmdlets $xmlWriter $Metadata $GlobalMetadata $associatedEntityType $keys $navigationProperties $CmdletAdapter $compexTypeMapping $true + + $nonKeyProperties = $associatedEntityType.EntityProperties | ? { -not $_.isKey } + $nullableProperties = $nonKeyProperties | ? { $_.isNullable } + $nonNullableProperties = $nonKeyProperties | ? { -not $_.isNullable } + + $xmlWriter.WriteStartElement('StaticCmdlets') + + $keyProperties = $keys + + GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $compexTypeMapping + + $entityActions = $Metadata.Actions | Where-Object { $_.EntitySet.Name -eq $associatedEntityType.Name } + + if ($entityActions.Length -gt 0) + { + foreach($action in $entityActions) + { + $xmlWriter = SaveCDXMLAction $xmlWriter $Metadata $action $EntitySet.Name $true $keys $complexTypeMapping + } + } + + $entityFunctions = $Metadata.Functions | Where-Object { $_.EntitySet.Name -eq $associatedEntityType.Name } + + if ($entityFunctions.Length -gt 0) + { + foreach($function in $entityFunctions) + { + $xmlWriter = SaveCDXMLFunction $xmlWriter $Metadata $function $associatedEntityType.Name $true $keys $complexTypeMapping + } + } + + $xmlWriter.WriteEndElement() + + $normalizedDotNetNamespace = GetNamespace $associatedEntityType.Namespace $normalizedNamespaces + $normalizedDotNetAlias = GetNamespace $associatedEntityType.Alias $normalizedNamespaces + + $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') + + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'EntityTypeAliasName') + if (!$associatedEntityType.Alias) + { + $xmlWriter.WriteString("") + } + else + { + $xmlWriter.WriteString("$($normalizedDotNetAlias).$($associatedEntityType.Name)") + } + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'EntityTypeName') + $xmlWriter.WriteString("$($normalizedDotNetNamespace).$($associatedEntityType.Name)") + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'IsSingleton') + $xmlWriter.WriteString("True") + $xmlWriter.WriteEndElement() + + # Add information about navigation properties and their types + # Used in scenario where user requests navigation property in -Select query + foreach ($navProperty in $navigationProperties) + { + if ($navProperty) + { + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', $navProperty.Name + 'NavigationProperty') + $xmlWriter.WriteString($navProperty.Type) + $xmlWriter.WriteEndElement() + } + } + + # Add UpdateRequestMethod to privateData + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod') + $xmlWriter.WriteString("$UpdateRequestMethod") + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteEndElement() + + SaveCDXMLFooter $xmlWriter + + Write-Verbose ($LocalizedData.VerboseSavedCDXML -f $($associatedEntityType.Name), $Path) + } +} + +######################################################### +# Saves InstanceCmdlets node to CDXML +######################################################### +function SaveCDXMLInstanceCmdlets +{ + param + ( + [System.XMl.XmlTextWriter] $xmlWriter, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [ODataUtils.EntityTypeV4] $EntityType, + $keys, + $navigationProperties, + $CmdletAdapter, + [Hashtable] $complexTypeMapping, + [bool] $isSingleton + ) + + if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLInstanceCmdlets") } + if($Metadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLInstanceCmdlets") } + + $xmlWriter.WriteStartElement('InstanceCmdlets') + $xmlWriter.WriteStartElement('GetCmdletParameters') + # adding key parameters and association parameters to QueryableProperties, each in a different parameter set + # to be used by GET cmdlet + if (($keys.Length -gt 0) -or ($navigationProperties.Length -gt 0)) + { + $querableNavProperties = @{} + + if ($isSingleton -eq $false) + { + foreach ($navProperty in $navigationProperties) + { + if ($navProperty -ne $null) + { + $associatedType = GetAssociatedType $Metadata $GlobalMetadata $navProperty + $associatedTypeKeyProperties = $associatedType.EntityProperties | ? { $_.IsKey } + + # Make sure associated parameter (based on navigation property) has EntitySet or Singleton, which makes it accessible from the service root + # Otherwise the Uri for associated navigation property won't be valid + if ($associatedTypeKeyProperties.Length -gt 0 -and (ShouldBeAssociatedParameter $GlobalMetadata $EntityType $associatedType $isSingleton)) + { + $querableNavProperties.Add($navProperty, $associatedTypeKeyProperties) + } + } + } + } + + $defaultCmdletParameterSet = 'Default' + if ($isSingleton -eq $true -and $querableNavProperties.Count -gt 0) + { + foreach($item in $querableNavProperties.GetEnumerator()) + { + $defaultCmdletParameterSet = $item.Key.Name + break + } + } + $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', $defaultCmdletParameterSet) + + + $xmlWriter.WriteStartElement('QueryableProperties') + + $position = 0 + + if ($isSingleton -eq $false) + { + $keys | ? { $_ -ne $null } | % { + $xmlWriter.WriteStartElement('Property') + $xmlWriter.WriteAttributeString('PropertyName', $_.Name) + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('RegularQuery') + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $_.Name) + $xmlWriter.WriteAttributeString('CmdletParameterSets', 'Default') + $xmlWriter.WriteAttributeString('IsMandatory', $_.IsMandatory.ToString().ToLower()) + $xmlWriter.WriteAttributeString('Position', $position) + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + + $position++ + } + } + + if ($querableNavProperties.Count -gt 0) + { + foreach($item in $querableNavProperties.GetEnumerator()) + { + $xmlWriter.WriteStartElement('Property') + $xmlWriter.WriteAttributeString('PropertyName', $item.Key.Name + ':' + $item.Value.Name + ':Key') + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $item.Value.TypeName $complexTypeMapping + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('RegularQuery') + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', 'Associated' + $item.Key.Name + $item.Value.Name) + $xmlWriter.WriteAttributeString('CmdletParameterSets', $item.Key.Name) + $xmlWriter.WriteAttributeString('IsMandatory', 'false') + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + } + + if ($isSingleton -eq $false) + { + # Add Query Parameters (i.e., Top, Skip, OrderBy, Filter) to the generated Get-* cmdlets. + $queryParameters = + @{ + "Filter" = "Edm.String"; + "IncludeTotalResponseCount" = "switch"; + "OrderBy" = "Edm.String"; + "Select" = "Edm.String"; + "Skip" = "Edm.Int32"; + "Top" = "Edm.Int32"; + } + } + else + { + $queryParameters = + @{ + "Select" = "Edm.String"; + } + } + + foreach($currentQueryParameter in $queryParameters.Keys) + { + $xmlWriter.WriteStartElement('Property') + $xmlWriter.WriteAttributeString('PropertyName', "QueryOption:" + $currentQueryParameter) + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $queryParameters[$currentQueryParameter] + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('RegularQuery') + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $currentQueryParameter) + + if($queryParameters[$currentQueryParameter] -eq "Edm.String") + { + $xmlWriter.WriteStartElement('ValidateNotNullOrEmpty') + $xmlWriter.WriteEndElement() + } + + if($queryParameters[$currentQueryParameter] -eq "Edm.Int32") + { + $xmlWriter.WriteStartElement('ValidateRange') + $xmlWriter.WriteAttributeString('Min', "1") + $xmlWriter.WriteAttributeString('Max', [int]::MaxValue) + $xmlWriter.WriteEndElement() + } + + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + + + $xmlWriter.WriteEndElement() + } + + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('GetCmdlet') + $xmlWriter.WriteStartElement('CmdletMetadata') + $xmlWriter.WriteAttributeString('Verb', 'Get') + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteEndElement() +} + +# Helper Method +# Returns true if navigation property of $AssociatedType has coresponding EntitySet or Singleton +# If yes, then it should become an associated parameter in CDXML +function ShouldBeAssociatedParameter +{ + param + ( + [System.Collections.ArrayList] $GlobalMetadata, + [ODataUtils.EntityTypeV4] $EntityType, + [ODataUtils.EntityTypeV4] $AssociatedType + ) + + # Check if associated type has navigation property, which links back to current type + $associatedTypeNavProperties = $AssociatedType.NavigationProperties | ? { + $_.Type -eq ($EntityType.Namespace + "." + $EntityType.Name) -or + $_.Type -eq ($EntityType.Alias + "." + $EntityType.Name) -or + $_.Type -eq ("Collection(" + $EntityType.Namespace + "." + $EntityType.Name + ")") -or + $_.Type -eq ("Collection(" + $EntityType.Alias + "." + $EntityType.Name + ")") + } + + if ($associatedTypeNavProperties.Length -lt 1) + { + return $false + } + + # Now check if associated parameter type (i.e, type of navigation property) has corresponding EntitySet or Singleton, + # which makes it accessible from the service root. + # Otherwise the Uri for associated navigation property won't be valid + foreach ($currentMetadata in $GlobalMetadata) + { + # Look for EntitySet with given type + foreach ($currentEntitySet in $currentMetadata.EntitySets) + { + if ($currentEntitySet.Type.Namespace -eq $EntityType.Namespace -and + $currentEntitySet.Type.Name -eq $EntityType.Name) + { + return $true + } + } + + # Look for Singleton with given type + foreach ($currentSingleton in $currentMetadata.Singletons) + { + if ($currentSingleton.Type.Namespace -eq $EntityType.Namespace -and + $currentSingleton.Type.Name -eq $EntityType.Name) + { + return $true + } + } + } + + return $false +} + +######################################################### +# Saves NewCmdlet node to CDXML +######################################################### +function SaveCDXMLNewCmdlet +{ + param + ( + [System.XMl.XmlTextWriter] $xmlWriter, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + $keyProperties, + $nonNullableProperties, + $nullableProperties, + $navigationProperties, + $CmdletAdapter, + $complexTypeMapping + ) + + if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLNewCmdlet") } + if($Metadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLNewCmdlet") } + + $xmlWriter.WriteStartElement('Cmdlet') + $xmlWriter.WriteStartElement('CmdletMetadata') + $xmlWriter.WriteAttributeString('Verb', 'New') + $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') + $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Method') + $xmlWriter.WriteAttributeString('MethodName', 'Create') + $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') + + AddParametersNode $xmlWriter $keyProperties $nonNullableProperties $nullableProperties $null $true $true $complexTypeMapping + + $xmlWriter.WriteEndElement() + + $navigationProperties | ? { $_ -ne $null } | % { + $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ + $associatedEntitySet = GetEntitySetForEntityType $Metadata $associatedType + + $xmlWriter.WriteStartElement('Method') + $xmlWriter.WriteAttributeString('MethodName', "Association:Create:$($associatedEntitySet.Name)") + $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) + + $associatedKeys = ($associatedType.EntityProperties | ? { $_.isKey }) + + AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping + + $xmlWriter.WriteEndElement() + } + + $xmlWriter.WriteEndElement() +} + +######################################################### +# Get corresponding EntityType for given EntitySet +######################################################### +function GetEntitySetForEntityType { + param( + [ODataUtils.MetadataV4] $Metadata, + [ODataUtils.EntityTypeV4] $entityType + ) + + if($entityType -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetEntitySetForEntityType") } + + $result = $Metadata.EntitySets | ? { ($_.Type.Namespace -eq $entityType.Namespace) -and ($_.Type.Name -eq $entityType.Name) } + + if (($result.Count -eq 0) -and ($entityType.BaseType -ne $null)) + { + GetEntitySetForEntityType $Metadata $entityType.BaseType + } + elseif ($result.Count -gt 1) + { + throw ($LocalizedData.WrongCountEntitySet -f (($entityType.Namespace + "." + $entityType.Name), $result.Count)) + } + + $result +} + +######################################################### +# Saves RemoveCmdlet node to CDXML +######################################################### +function SaveCDXMLRemoveCmdlet +{ + param + ( + [System.XMl.XmlTextWriter] $xmlWriter, + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + $keyProperties, + $navigationProperties, + $CmdletAdapter, + $complexTypeMapping + ) + + if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLRemoveCmdlet") } + if($Metadata -eq $null) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLRemoveCmdlet") } + + $xmlWriter.WriteStartElement('Cmdlet') + $xmlWriter.WriteStartElement('CmdletMetadata') + $xmlWriter.WriteAttributeString('Verb', 'Remove') + $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') + $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Method') + $xmlWriter.WriteAttributeString('MethodName', 'Delete') + $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') + + AddParametersNode $xmlWriter $keyProperties $nul $null $null $true $true $complexTypeMapping + + $xmlWriter.WriteEndElement() + + $navigationPrperties | ? { $_ -ne $null } | % { + + $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ + $associatedEntitySet = GetEntitySetForEntityType $Metadata $associatedType + + $xmlWriter.WriteStartElement('Method') + $xmlWriter.WriteAttributeString('MethodName', "Association:Delete:$($associatedEntitySet.Name)") + $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) + + $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ + $associatedKeys = ($associatedType.EntityProperties | ? { $_.isKey }) + + AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping + + $xmlWriter.WriteEndElement() + } + $xmlWriter.WriteEndElement() +} + +######################################################### +# Gets associated instance's EntityType for a given navigation property +######################################################### +function GetAssociatedType { + param( + [ODataUtils.MetadataV4] $Metadata, + [System.Collections.ArrayList] $GlobalMetadata, + [ODataUtils.NavigationPropertyV4] $navProperty + ) + + $associationType = $navProperty.Type + $associationType = $associationType.Replace($Metadata.Namespace + ".", "") + $associationType = $associationType.Replace($Metadata.Alias + ".", "") + $associationType = $associationType.Replace("Collection(", "") + $associationType = $associationType.Replace(")", "") + + $associatedType = $Metadata.EntityTypes | ? { $_.Name -eq $associationType } + + if (!$associatedType -and $GlobalMetadata -ne $null) + { + $associationFullTypeName = $navProperty.Type.Replace("Collection(", "").Replace(")", "") + + foreach ($referencedMetadata in $GlobalMetadata) + { + if (($associatedType = $referencedMetadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName }) -ne $null -or + ($associatedType = $referencedMetadata.ComplexTypes | Where { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName }) -ne $null -or + ($associatedType = $referencedMetadata.EnumTypes | Where { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName }) -ne $null) + { + # Found associated class + break + } + } + } + + if ($associatedType.Count -lt 1) + { + throw ($LocalizedData.AssociationNotFound -f $associationType) + } + elseif ($associatedType.Count -gt 1) + { + throw ($LocalizedData.TooManyMatchingAssociationTypes -f $associatedType.Count, $associationType) + } + + # return associated EntityType + $associatedType +} + +######################################################### +# Saves CDXML for Instance/Service level actions +######################################################### +function SaveCDXMLAction +{ + param + ( + [System.Xml.XmlWriter] $xmlWriter, + [ODataUtils.ActionV4] $action, + [AllowEmptyString()] + [string] $noun, + [bool] $isInstanceAction, + [ODataUtils.TypeProperty] $keys, + [Hashtable] $complexTypeMapping + ) + + if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLAction") } + if($action -eq $null) { throw ($LocalizedData.ArguementNullError -f "action", "SaveCDXMLAction") } + + $xmlWriter.WriteStartElement('Cmdlet') + + $xmlWriter.WriteStartElement('CmdletMetadata') + $xmlWriter.WriteAttributeString('Verb', 'Invoke') + $xmlWriter.WriteAttributeString('Noun', "$($noun)$($action.Name)") + $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Method') + $xmlWriter.WriteAttributeString('MethodName', "Action:$($action.Name):$($action.EntitySet.Name)") + + $xmlWriter.WriteStartElement('Parameters') + + $keys | ? { $_ -ne $null } | % { + $xmlWriter.WriteStartElement('Parameter') + $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key') + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $_.Name) + $xmlWriter.WriteAttributeString('IsMandatory', 'true') + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + + $i = -1 + foreach ($parameter in $action.Parameters) + { + $i++ + + # for Instance actions, first parameter is Entity Set which we refer to using keys + if ($isInstanceAction -and ($i -eq 0)) + { + continue + } + + $xmlWriter.WriteStartElement('Parameter') + $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name) + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $parameter.TypeName + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $parameter.Name) + if (-not $parameter.IsNullable) + { + $xmlWriter.WriteAttributeString('IsMandatory', 'true') + } + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + + # Add -Force parameter to Service Action cmdlets. + AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping + + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteEndElement() + + $xmlWriter +} + +######################################################### +# Saves CDXML for Instance/Service level functions +######################################################### +function SaveCDXMLFunction +{ + param + ( + [System.Xml.XmlWriter] $xmlWriter, + [ODataUtils.FunctionV4] $function, + [AllowEmptyString()] + [string] $noun, + [bool] $isInstanceAction, + [ODataUtils.TypeProperty] $keys, + [Hashtable] $complexTypeMapping + ) + + if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLFunction") } + if($function -eq $null) { throw ($LocalizedData.ArguementNullError -f "function", "SaveCDXMLFunction") } + + $xmlWriter.WriteStartElement('Cmdlet') + + $xmlWriter.WriteStartElement('CmdletMetadata') + $xmlWriter.WriteAttributeString('Verb', 'Invoke') + $xmlWriter.WriteAttributeString('Noun', "$($noun)$($function.Name)") + $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Method') + if (!$function.EntitySet) + { + $xmlWriter.WriteAttributeString('MethodName', "Action:$($function.Name):$($function.ReturnType)") + } + else + { + $xmlWriter.WriteAttributeString('MethodName', "Action:$($function.Name):$($function.EntitySet)") + } + + $xmlWriter.WriteStartElement('Parameters') + + $keys | ? { $_ -ne $null } | % { + $xmlWriter.WriteStartElement('Parameter') + $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key') + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $_.Name) + $xmlWriter.WriteAttributeString('IsMandatory', 'true') + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + + $i = -1 + foreach ($parameter in $function.Parameters) + { + $i++ + + # for Instance actions, first parameter is Entity Set which we refer to using keys + if ($isInstanceAction -and ($i -eq 0)) + { + continue + } + + $xmlWriter.WriteStartElement('Parameter') + $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name) + + $xmlWriter.WriteStartElement('Type') + $PSTypeName = Convert-ODataTypeToCLRType $parameter.Type + $xmlWriter.WriteAttributeString('PSType', $PSTypeName) + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('CmdletParameterMetadata') + $xmlWriter.WriteAttributeString('PSName', $parameter.Name) + if (-not $parameter.IsNullable) + { + $xmlWriter.WriteAttributeString('IsMandatory', 'true') + } + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + } + + # Add -Force parameter to Service Function cmdlets. + AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping + + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteEndElement() + + $xmlWriter +} + +######################################################### +# Saves CDXML for Service-level actions and functions +######################################################### +function SaveServiceActionsCDXML +{ + param + ( + [System.Collections.ArrayList] $GlobalMetadata, + [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, + [string] $Path, + [Hashtable] $complexTypeMapping, + [string] $progressBarStatus, + [string] $CmdletAdapter + ) + + $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) + + if ($xmlWriter -eq $null) + { + throw $LocalizedData.XmlWriterInitializationError -f "ServiceActions" + } + + $xmlWriter = SaveCDXMLHeader $xmlWriter $ODataEndpointProxyParameters.Uri 'ServiceActions' 'ServiceActions' -CmdletAdapter $CmdletAdapter + + $actions = @() + $functions = @() + + foreach ($Metadata in $GlobalMetadata) + { + $actions += $Metadata.Actions | Where-Object { $_.EntitySet -eq '' -or $_.EntitySet -eq $null } + $functions += $Metadata.Functions | Where-Object { $_.EntitySet -eq '' -or $_.EntitySet -eq $null } + } + + if ($actions.Length -gt 0 -or $functions.Length -gt 0) + { + $xmlWriter.WriteStartElement('StaticCmdlets') + } + + # Save actions + if ($actions.Length -gt 0) + { + foreach ($action in $actions) + { + if ($action -ne $null) + { + $xmlWriter = SaveCDXMLAction $xmlWriter $action '' $false $null $complexTypeMapping + } + } + } + + # Save functions + if ($functions.Length -gt 0) + { + foreach ($function in $functions) + { + if ($function -ne $null) + { + $xmlWriter = SaveCDXMLFunction $xmlWriter $function '' $false $null $complexTypeMapping + } + } + } + + if ($actions.Length -gt 0 -or $functions.Length -gt 0) + { + $xmlWriter.WriteEndElement() + } + + $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'Namespace') + $xmlWriter.WriteString("$($EntitySet.Namespace)") + $xmlWriter.WriteEndElement() + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'Alias') + if (!$EntitySet.Alias) + { + $xmlWriter.WriteString("") + } + else + { + $xmlWriter.WriteString("$($EntitySet.Alias)") + } + $xmlWriter.WriteEndElement() + + $xmlWriter.WriteStartElement('Data') + $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod') + $xmlWriter.WriteString("Post") + $xmlWriter.WriteEndElement() + $xmlWriter.WriteEndElement() + + SaveCDXMLFooter $xmlWriter + + Write-Verbose ($LocalizedData.VerboseSavedServiceActions -f $Path) + + # Write progress bar message + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 60 20 1 1 +} + +######################################################### +# GenerateModuleManifest is a helper function used +# to generate a wrapper module manifest file. The +# generated module manifest is persisted to the disk at +# the specified OutputModule path. When the module +# manifest is imported, the following comands will +# be imported: +# 1. Get, Set, New & Remove proxy cmdlets for entity +# sets and singletons. +# 2. If the server side Odata endpoint exposes complex +# types, enum types, type definitions, then the corresponding +# client side proxy types imported. +# 3. Service Action/Function proxy cmdlets. +######################################################### +function GenerateModuleManifest +{ + param + ( + [System.Collections.ArrayList] $GlobalMetadata, + [String] $ModulePath, + [string[]] $AdditionalModules, + [Hashtable] $resourceNameMappings, + [string] $progressBarStatus + ) + + if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "progressBarStatus", "GenerateModuleManifest") } + + $NestedModules = @() + + foreach ($Metadata in $GlobalMetadata) + { + foreach ($entitySet in $Metadata.EntitySets) + { + $entitySetName = $entitySet.Name + if(($null -ne $resourceNameMappings) -and + $resourceNameMappings.ContainsKey($entitySetName)) + { + $entitySetName = $resourceNameMappings[$entitySetName] + } + else + { + $entitySetName = $entitySet.Type.Name + } + + $NestedModules += "$OutputModule\$($entitySetName).cdxml" + } + + foreach ($singleton in $Metadata.SingletonTypes) + { + $singletonName = $singleton.Name + $NestedModules += "$OutputModule\$($singletonName)" + "Singleton" + ".cdxml" + } + } + + New-ModuleManifest -Path $ModulePath -NestedModules ($AdditionalModules + $NestedModules) + + Write-Verbose ($LocalizedData.VerboseSavedModuleManifest -f $ModulePath) + + # Update the Progress Bar. + ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 80 20 1 1 +} + +######################################################### +# This is a helper function used to generate comlplex +# type defination from the metadata. +######################################################### +function GenerateComplexTypeDefinition +{ + param + ( + [System.Collections.ArrayList] $GlobalMetadata, + [string] $OutputModule, + [string] $typeDefinationFileName, + $normalizedNamespaces + ) + + $Path = "$OutputModule\$typeDefinationFileName" + $date = Get-Date + + $output = @" +# This module was generated by PSODataUtils on $date. + +`$typeDefinitions = @" +using System; +using System.Management.Automation; +using System.ComponentModel; + +"@ + # We are currently generating classes for EntityType & ComplexType + # defination exposed in the metadata. + + $complexTypeMapping = @{} + + # First, create complex type mappings for all metadata files at once + foreach ($metadata in $GlobalMetadata) + { + $typesToBeGenerated = $metadata.EntityTypes+$metadata.ComplexTypes + $enumTypesToBeGenerated = $metadata.EnumTypes + $typeDefinitionsToBeGenerated = $metadata.TypeDefinitions + + foreach ($entityType in $typesToBeGenerated) + { + if ($entityType -ne $null) + { + $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name + if(!$complexTypeMapping.ContainsKey($entityTypeFullName)) + { + $complexTypeMapping.Add($entityTypeFullName, $entityType.Name) + } + + # In short name we use Alias instead of Namespace + # We will add short name to $complexTypeMapping to enable Alias based search + if ($entityType.Alias -ne $null -and $entityType.Alias -ne "") + { + $entityTypeShortName = $entityType.Alias + '.' + $entityType.Name + if(!$complexTypeMapping.ContainsKey($entityTypeShortName)) + { + $complexTypeMapping.Add($entityTypeShortName, $entityType.Name) + } + } + } + } + + foreach ($enumType in $enumTypesToBeGenerated) + { + if ($enumType -ne $null) + { + $enumTypeFullName = $enumType.Namespace + '.' + $enumType.Name + if(!$complexTypeMapping.ContainsKey($enumTypeFullName)) + { + $complexTypeMapping.Add($enumTypeFullName, $enumType.Name) + } + + if (($enumType.Alias -ne $null -and $enumType.Alias -ne "") -or ($metadata.Alias -ne $null -and $metadata.Alias -ne "")) + { + if ($enumType.Alias -ne $null -and $enumType.Alias -ne "") + { + $alias = $enumType.Alias + } + else + { + $alias = $metadata.Alias + } + + $enumTypeShortName = $alias + '.' + $enumType.Name + if(!$complexTypeMapping.ContainsKey($enumTypeShortName)) + { + $complexTypeMapping.Add($enumTypeShortName, $enumType.Name) + } + } + } + } + + foreach ($typeDefinition in $typeDefinitionsToBeGenerated) + { + if ($typeDefinition -ne $null) + { + $typeDefinitionFullName = $typeDefinition.Namespace + '.' + $typeDefinition.Name + if(!$complexTypeMapping.ContainsKey($typeDefinitionFullName)) + { + $complexTypeMapping.Add($typeDefinitionFullName, $typeDefinition.Name) + } + + # In short name we use Alias instead of Namespace + # We will add short name to $complexTypeMapping to enable Alias based search + if ($typeDefinition.Alias) + { + $typeDefinitionShortName = $typeDefinition.Alias + '.' + $typeDefinition.Name + if(!$complexTypeMapping.ContainsKey($typeDefinitionShortName)) + { + $complexTypeMapping.Add($typeDefinitionShortName, $typeDefinition.Name) + } + } + } + } + } + + # Now classes definitions will be generated + foreach ($metadata in $GlobalMetadata) + { + $typesToBeGenerated = $metadata.EntityTypes+$metadata.ComplexTypes + $enumTypesToBeGenerated = $metadata.EnumTypes + $typeDefinitionsToBeGenerated = $metadata.TypeDefinitions + + if($typesToBeGenerated.Count -gt 0 -or $enumTypesToBeGenerated.Count -gt 0) + { + if ($metadata.Alias -ne $null -and $metadata.Alias -ne "") + { + # Check if this namespace has to be normalized in the .Net namespace/class definitions file. + $dotNetAlias = GetNamespace $metadata.Alias $normalizedNamespaces + + $output += @" + +namespace $($dotNetAlias) +{ +"@ + } + else + { + # Check if this namespace has to be normalized in the .Net namespace/class definitions file. + $dotNetNamespace = GetNamespace $metadata.Namespace $normalizedNamespaces + + $output += @" + +namespace $($dotNetNamespace) +{ +"@ + } + + foreach ($typeDefinition in $typeDefinitionsToBeGenerated) + { + if ($typeDefinition -ne $null) + { + Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $typeDefinitionFullName, "$OutputModule\$typeDefinationFileName") + + $output += "`n public class $($typeDefinition.Name)`n {" + $typeName = Convert-ODataTypeToCLRType $typeDefinition.BaseTypeStr $complexTypeMapping + $dotNetPropertyNamespace = GetNamespace $typeName $normalizedNamespaces $true + $output += "`n public $dotNetPropertyNamespace value;" + $output += @" +`n } +"@ + } + } + + $DotNETKeywords = ("abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "partial", "remove", "select", "set", "value", "var", "where", "yield") + + foreach ($enumType in $enumTypesToBeGenerated) + { + if ($enumType -ne $null) + { + $enumTypeFullName = $enumType.Namespace + '.' + $enumType.Name + + Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $enumTypeFullName, "$OutputModule\$typeDefinationFileName") + + $output += "`n public enum $($enumType.Name)`n {" + + $properties = $null + + for($index = 0; $index -lt $enumType.Members.Count; $index++) + { + $memberName = $enumType.Members[$index].Name + $formattedMemberName = [System.Text.RegularExpressions.Regex]::Replace($memberName, "[^0-9a-zA-Z]", "_"); + $memberValue = $enumType.Members[$index].Value + + if ($DotNETKeywords -contains $formattedMemberName) + { + # If member name is a known keyword in .Net, add '@' prefix + $formattedMemberName = '@' + $formattedMemberName + } + + if ($formattedMemberName -match "^[0-9]*$") + { + # If member name is a numeric value, add 'm_' prefix + $formattedMemberName = 'm_' + $formattedMemberName + } + + if ($memberName -ne $formattedMemberName -or $formattedMemberName -like '@*' -or $formattedMemberName -like 'm_*') + { + # Add Description attribute to preserve original value + $properties += "`n [Description(`"$($memberName)`")]$formattedMemberName" + } + else + { + $properties += "`n $memberName" + } + + if ($memberValue) + { + $properties += " = $memberValue," + } + else + { + $properties += "," + } + } + + $output += $properties + $output += @" +`n } +"@ + } + } + + foreach ($entityType in $typesToBeGenerated) + { + if ($entityType -ne $null) + { + $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name + + Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $entityTypeFullName, "$OutputModule\$typeDefinationFileName") + + if ($entityType.BaseTypeStr -ne $null -and $entityType.BaseTypeStr -ne '' -and $entityType.BaseType -eq $null) + { + # This class inherits from another class, but we were not able to find base class during Parsing. + # We'll make another attempt. + foreach ($referencedMetadata in $GlobalMetadata) + { + if (($baseType = $referencedMetadata.EntityTypes | Where { $_.Namespace + "." + $_.Name -eq $entityType.BaseTypeStr -or $_.Alias + "." + $_.Name -eq $entityType.BaseTypeStr }) -ne $null -or + ($baseType = $referencedMetadata.ComplexTypes | Where { $_.Namespace + "." + $_.Name -eq $entityType.BaseTypeStr -or $_.Alias + "." + $_.Name -eq $entityType.BaseTypeStr }) -ne $null) + { + # Found base class + $entityType.BaseType = $baseType + break + } + } + } + + if($null -ne $entityType.BaseType) + { + if ((![string]::IsNullOrEmpty($entityType.BaseType.Alias) -and $entityType.BaseType.Alias -eq $entityType.Alias) -or + (![string]::IsNullOrEmpty($entityType.BaseType.Namespace) -and $entityType.BaseType.Namespace -eq $entityType.Namespace)) + { + $fullBaseTypeName = $entityType.BaseType.Name + } + else + { + # Base type can be defined in different namespace. For that reason we include namespace or alias. + if (![string]::IsNullOrEmpty($entityType.BaseType.Alias)) + { + # Check if derived alias has to be normalized. + $normalizedDotNetAlias = GetNamespace $entityType.BaseType.Alias $normalizedNamespaces + $fullBaseTypeName = $normalizedDotNetAlias + "." + $entityType.BaseType.Name + } + else + { + # Check if derived namespace has to be normalized. + $normalizedDotNetNamespace = GetNamespace $entityType.BaseType.Namespace $normalizedNamespaces + $fullBaseTypeName = $normalizedDotNetNamespace + "." + $entityType.BaseType.Name + } + } + + $output += "`n public class $($entityType.Name) : $($fullBaseTypeName)`n {" + } + else + { + $output += "`n public class $($entityType.Name)`n {" + } + + $properties = $null + + for($index = 0; $index -lt $entityType.EntityProperties.Count; $index++) + { + $property = $entityType.EntityProperties[$index] + $typeName = Convert-ODataTypeToCLRType $property.TypeName $complexTypeMapping + + if ($typeName.StartsWith($metadata.Namespace + ".")) + { + $dotNetPropertyNamespace = $typeName.Replace($metadata.Namespace + ".", "") + } + elseif ($typeName.StartsWith($metadata.Alias + ".")) + { + $dotNetPropertyNamespace = $typeName.Replace($metadata.Alias + ".", "") + } + else + { + $dotNetPropertyNamespace = GetNamespace $typeName $normalizedNamespaces $true + } + + $properties += "`n public $dotNetPropertyNamespace $($property.Name);" + } + + $output += $properties + $output += @" +`n } +"@ + } + } + + $output += "`n}`n" + } + } + $output += """@`n" + $output += "Add-Type -TypeDefinition `$typeDefinitions -IgnoreWarnings`n" + $output | Out-File -FilePath $Path + Write-Verbose ($LocalizedData.VerboseSavedTypeDefinationModule -f $typeDefinationFileName, $OutputModule) + + return $complexTypeMapping +} \ No newline at end of file diff --git a/src/Modules/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 b/src/Modules/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 new file mode 100644 index 000000000..6010408f1 Binary files /dev/null and b/src/Modules/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 differ diff --git a/src/Modules/map.json b/src/Modules/map.json index f42e2a356..c3b277a68 100644 --- a/src/Modules/map.json +++ b/src/Modules/map.json @@ -20,5 +20,11 @@ "monad/src/LocalAccounts/LocalAccounts.format.ps1xml": "Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml", "monad/src/oneget/PowerShell.Module/PackageManagement.psd1": "PackageManagement/PackageManagement.psd1", "monad/src/oneget/PowerShell.Module/PackageManagement.format.ps1xml": "PackageManagement/PackageManagement.format.ps1xml", - "monad/src/oneget/providers/inbox/powershell.metaprovider/PackageProviderFunctions.psm1": "PackageManagement/PackageProviderFunctions.psm1" + "monad/src/oneget/providers/inbox/powershell.metaprovider/PackageProviderFunctions.psm1": "PackageManagement/PackageProviderFunctions.psm1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1": "Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1": "Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1": "Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1": "Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1": "Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1", + "wmi/psws/PSODataUtils/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsStrings.psd1": "Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1" }