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"
}