Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added support for configuration document variables through `New-DscConfigurationDocument`.
- Added dsc.exe-based discovery for `Import-DscAuthoringResource` and `Import-DscResourceAuthoringMetadata`, including adapted resource metadata.
- Added support for `[ValidatePattern()]` attributes on DSC properties, emitting the regex as a `pattern` keyword in the generated JSON schema.
- Added `-AllowNonEcmaPattern` switch to `New-DscAdaptedResourceManifest` to force-emit patterns containing .NET-specific regex constructs that are not ECMA 262 compatible.

### Fixed

- Fixed build task import so module aliases are correctly exported when the module is loaded.
- Fixed `[ValidateSet()]` attributes on `[string]` DSC properties now being correctly emitted as `enum` in the generated JSON schema.
- Fixed generated authoring types to initialize JSON Schema `default` values and expose JSON Schema `enum` values as validated .NET enum properties.
- Fixed exported configuration documents to preserve `$schema` as the first top-level property.
- Fixed `UTF8BOM` issue on new script.
- Fixed taskss not being updated in the manifest.

Expand Down
315 changes: 315 additions & 0 deletions source/Classes/005.MicrosoftDscAuthoringTypes.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
<#
.SYNOPSIS
Defines the core Microsoft.DSC authoring types.
#>
if (-not ('Microsoft.DSC.Configuration' -as [System.Type]))
{
$microsoftDscAuthoringTypes = @'
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;

namespace Microsoft.DSC
{
public sealed class Expression
{
public string Value { get; set; }

public Expression()
{
}

public Expression(string value)
{
this.Value = value;
}

public override string ToString()
{
return this.Value;
}

public static implicit operator string(Expression expression)
{
return expression == null ? null : expression.Value;
}
}

public sealed class Directives
{
public string SecurityContext { get; set; }

public OrderedDictionary ToHashtable()
{
OrderedDictionary result = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

if (!String.IsNullOrEmpty(this.SecurityContext))
{
result["securityContext"] = this.SecurityContext;
}

return result;
}
}

public sealed class ResourceDirectives
{
public string RequireAdapter { get; set; }

public OrderedDictionary ToHashtable()
{
OrderedDictionary result = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

if (!String.IsNullOrEmpty(this.RequireAdapter))
{
result["requireAdapter"] = this.RequireAdapter;
}

return result;
}
}

public sealed class Resource
{
public string Name { get; set; }
public string Type { get; set; }
public object Properties { get; set; }
public string[] DependsOn { get; set; }
public ResourceDirectives Directives { get; set; }
public Hashtable Metadata { get; set; }

public Resource()
{
this.Metadata = new Hashtable(StringComparer.OrdinalIgnoreCase);
}

public OrderedDictionary ToHashtable()
{
OrderedDictionary result = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

if (!String.IsNullOrEmpty(this.Name))
{
result["name"] = this.Name;
}

if (!String.IsNullOrEmpty(this.Type))
{
result["type"] = this.Type;
}

if (this.Properties != null)
{
result["properties"] = AuthoringData.Normalize(this.Properties);
}

if (this.DependsOn != null && this.DependsOn.Length > 0)
{
result["dependsOn"] = this.DependsOn;
}

if (this.Directives != null)
{
OrderedDictionary directives = this.Directives.ToHashtable();

if (directives.Count > 0)
{
result["directives"] = directives;
}
}

if (this.Metadata != null && this.Metadata.Count > 0)
{
result["metadata"] = AuthoringData.Normalize(this.Metadata);
}

return result;
}
}

public sealed class Configuration
{
public string Schema { get; set; }
public object Parameters { get; set; }
public object Variables { get; set; }
public Directives Directives { get; set; }
public List<Resource> Resources { get; set; }
public Hashtable Metadata { get; set; }

public Configuration()
{
this.Schema = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json";
this.Resources = new List<Resource>();
this.Metadata = new Hashtable(StringComparer.OrdinalIgnoreCase);
}

public OrderedDictionary ToHashtable()
{
OrderedDictionary result = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

if (!String.IsNullOrEmpty(this.Schema))
{
result["$schema"] = this.Schema;
}

if (this.Parameters != null)
{
result["parameters"] = AuthoringData.Normalize(this.Parameters);
}

if (this.Variables != null)
{
result["variables"] = AuthoringData.Normalize(this.Variables);
}

if (this.Directives != null)
{
OrderedDictionary directives = this.Directives.ToHashtable();

if (directives.Count > 0)
{
result["directives"] = directives;
}
}

if (this.Resources != null && this.Resources.Count > 0)
{
ArrayList resources = new ArrayList();

foreach (Resource resource in this.Resources)
{
resources.Add(resource.ToHashtable());
}

result["resources"] = resources;
}

if (this.Metadata != null && this.Metadata.Count > 0)
{
result["metadata"] = AuthoringData.Normalize(this.Metadata);
}

return result;
}
}

internal static class AuthoringData
{
internal static object Normalize(object value)
{
if (value == null)
{
return null;
}

if (value is string)
{
return value;
}

Expression expression = value as Expression;

if (expression != null)
{
return expression.Value;
}

MethodInfo toHashtable = value.GetType().GetMethod("ToHashtable", Type.EmptyTypes);

if (toHashtable != null && toHashtable.DeclaringType != typeof(Hashtable))
{
return toHashtable.Invoke(value, null);
}

IDictionary dictionary = value as IDictionary;

if (dictionary != null)
{
OrderedDictionary result = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

foreach (DictionaryEntry entry in dictionary)
{
result[entry.Key] = Normalize(entry.Value);
}

return result;
}

IEnumerable enumerable = value as IEnumerable;

if (enumerable != null)
{
ArrayList result = new ArrayList();

foreach (object item in enumerable)
{
result.Add(Normalize(item));
}

return result;
}

Type valueType = value.GetType();

Type nullableType = Nullable.GetUnderlyingType(valueType);
if (nullableType != null)
{
PropertyInfo nullableValue = valueType.GetProperty("Value");
if (nullableValue != null)
{
return Normalize(nullableValue.GetValue(value, null));
}
}

if (valueType.IsEnum)
{
return value.ToString();
}

if (valueType.IsPrimitive || value is decimal || value is DateTime || value is Guid)
{
return value;
}

OrderedDictionary properties = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);

foreach (PropertyInfo property in valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!property.CanRead || property.GetIndexParameters().Length > 0)
{
continue;
}

MethodInfo shouldSerialize = valueType.GetMethod(
"ShouldSerialize" + property.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);

if (shouldSerialize != null && shouldSerialize.ReturnType == typeof(bool))
{
bool serializeProperty = (bool) shouldSerialize.Invoke(value, null);
if (!serializeProperty)
{
continue;
}
}

object propertyValue = property.GetValue(value, null);

if (propertyValue != null)
{
properties[property.Name] = Normalize(propertyValue);
}
}

return properties;
}
}
}
'@

Add-Type -TypeDefinition $microsoftDscAuthoringTypes -Language CSharp
}
Loading