/// <summary> /// Format the type name of a CIM property in a presentable way. /// </summary> /// <param name="prop"></param> /// <param name="isOptionalProperty"></param> /// <returns></returns> private static StringBuilder FormatCimPropertyType(DynamicKeywordProperty prop, bool isOptionalProperty) { string cimTypeName = prop.TypeConstraint; StringBuilder formattedTypeString = new StringBuilder(); if (string.Equals(cimTypeName, "MSFT_Credential", StringComparison.OrdinalIgnoreCase)) { formattedTypeString.Append("[PSCredential]"); } else if (string.Equals(cimTypeName, "MSFT_KeyValuePair", StringComparison.OrdinalIgnoreCase) || string.Equals(cimTypeName, "MSFT_KeyValuePair[]", StringComparison.OrdinalIgnoreCase)) { formattedTypeString.Append("[Hashtable]"); } else { string convertedTypeString = System.Management.Automation.LanguagePrimitives.ConvertTypeNameToPSTypeName(cimTypeName); if (!string.IsNullOrEmpty(convertedTypeString) && !string.Equals(convertedTypeString, "[]", StringComparison.OrdinalIgnoreCase)) { formattedTypeString.Append(convertedTypeString); } else { formattedTypeString.Append("[" + cimTypeName + "]"); } } // Do the property values map if (prop.ValueMap != null && prop.ValueMap.Count > 0) { formattedTypeString.Append(" { " + string.Join(" | ", prop.ValueMap.Keys.OrderBy(x => x)) + " }"); } // We prepend optional property with "[" so close out it here. This way it is shown with [ ] to indication optional if (isOptionalProperty) { formattedTypeString.Append("]"); } formattedTypeString.Append("\n"); return formattedTypeString; }
private StatementAst ConfigurationStatementRule(IEnumerable<AttributeAst> customAttributes, Token configurationToken) { //G configuration-statement: //G 'configuration' new-lines:opt singleNameExpression new-lines:opt statement-block //G singleNameExpression: //G command-argument //G primary-expression IScriptExtent startExtent = configurationToken.Extent; IScriptExtent endErrorStatement = startExtent; bool isError = false; // The expression that returns the configuration name. ExpressionAst configurationName; string simpleConfigurationNameValue = null; SkipNewlines(); Token configurationNameToken = NextToken(); Token configurationKeywordToken = configurationNameToken; if (configurationNameToken.Kind == TokenKind.LCurly) { ReportError(After(startExtent), () => ParserStrings.MissingConfigurationName); // Try reading the configuration body - this should keep the parse in sync - but we won't return it ScriptBlockExpressionRule(configurationNameToken); return null; } if (configurationNameToken.Kind == TokenKind.EndOfInput) { UngetToken(configurationNameToken); ReportIncompleteInput(After(configurationNameToken.Extent), () => ParserStrings.MissingConfigurationName); return null; } // Unget the configuration token so it can possibly be re-read as part of an expression UngetToken(configurationNameToken); // Finally read the name for this configuration configurationName = GetWordOrExpression(configurationNameToken); if (configurationName == null) { isError = true; ReportIncompleteInput(configurationNameToken.Extent, () => ParserStrings.MissingConfigurationName); } else { object outValue; if (IsConstantValueVisitor.IsConstant(configurationName, out outValue)) { simpleConfigurationNameValue = outValue as string; if (simpleConfigurationNameValue == null || !System.Text.RegularExpressions.Regex.IsMatch(simpleConfigurationNameValue, "^[A-Za-z][A-Za-z0-9_./-]*$")) { // This is actually a semantics check, the syntax is fine at this point. // Continue parsing to get as much information as possible isError = true; ReportError(configurationName.Extent, () => ParserStrings.InvalidConfigurationName, simpleConfigurationNameValue ?? string.Empty); } } } SkipNewlines(); // // Load the system classes and import them as keywords // Runspaces.Runspace localRunspace = null; bool topLevel = false; try { // At this point, we'll need a runspace to use to hold the metadata for the parse. If there is no // current runspace to use, we create one and set it to be the default for this thread... if (Runspaces.Runspace.DefaultRunspace == null) { localRunspace = Runspaces.RunspaceFactory.CreateRunspace(Runspaces.InitialSessionState.CreateDefault2()); localRunspace.ThreadOptions = Runspaces.PSThreadOptions.UseCurrentThread; localRunspace.Open(); Runspaces.Runspace.DefaultRunspace = localRunspace; } // Configuration is not supported on ARM or in ConstrainedLanguage if (PsUtils.IsRunningOnProcessorArchitectureARM() || Runspace.DefaultRunspace.ExecutionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) { ReportError(configurationToken.Extent, () => ParserStrings.ConfigurationNotAllowedInConstrainedLanguage, configurationToken.Kind.Text()); return null; } // Configuration is not supported on WinPE if (Utils.IsWinPEHost()) { ReportError(configurationToken.Extent, () => ParserStrings.ConfigurationNotAllowedOnWinPE, configurationToken.Kind.Text()); return null; } ExpressionAst configurationBodyScriptBlock = null; // Automatically import the PSDesiredStateConfiguration module at this point. PowerShell p = null; // Save the parser we're using so we can resume the current parse when we're done. var currentParser = Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser; Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser = new Parser(); try { if (localRunspace != null) { p = PowerShell.Create(); p.Runspace = localRunspace; } else { p = PowerShell.Create(RunspaceMode.CurrentRunspace); } try { // See of the default CIM keywords are already loaded. If they haven't been // then this is the top level. Record that information and then load the defaults // keywords. if (DynamicKeyword.GetKeyword("OMI_ConfigurationDocument") == null) { // Load the default CIM keywords Collection<Exception> CIMKeywordErrors = new Collection<Exception>(); Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); // Report any errors encountered while loading CIM dynamic keywords. if (CIMKeywordErrors.Count > 0) { ReportErrorsAsWarnings(CIMKeywordErrors); } // Load any keywords that have been defined earlier in the script. if (_configurationKeywordsDefinedInThisFile != null) { foreach (var kw in _configurationKeywordsDefinedInThisFile.Values) { if (!DynamicKeyword.ContainsKeyword(kw.Keyword)) { DynamicKeyword.AddKeyword(kw); } } } topLevel = true; } } catch (Exception e) { // This shouldn't happen - the system classes should always be good, but just in case, // we'll catch the exception and report it as an error. ReportError(configurationKeywordToken.Extent, () => e.ToString()); return null; } } finally { if (p != null) { p.Dispose(); } // // Put the parser back... // Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser = currentParser; } Token lCurly = NextToken(); if (lCurly.Kind != TokenKind.LCurly) { ReportIncompleteInput(After(lCurly.Extent), () => ParserStrings.MissingCurlyInConfigurationStatement); isError = true; UngetToken(lCurly); } else { var oldInConfiguration = _inConfiguration; try { _inConfiguration = true; configurationBodyScriptBlock = ScriptBlockExpressionRule(lCurly); } finally { _inConfiguration = oldInConfiguration; } if (configurationBodyScriptBlock == null) { ReportError(After(lCurly.Extent), () => ParserStrings.ConfigurationBodyEmpty); return null; } } if (isError) { return new ErrorStatementAst(ExtentOf(startExtent, endErrorStatement), configurationToken); } #region "Add Configuration Keywords" // If the configuration name is a constant string, then // if we're not at the top level, we'll add it to the list of configuration resource keywords. // If we are at the top level, then we'll add it to the list of keywords defined in this // parse so it can be used as a resource in subsequent config statements. var scAst = configurationName as StringConstantExpressionAst; if (scAst != null) { var keywordToAddForThisConfigurationStatement = new System.Management.Automation.Language.DynamicKeyword { BodyMode = DynamicKeywordBodyMode.Hashtable, ImplementingModule = _keywordModuleName, Keyword = scAst.Value, NameMode = DynamicKeywordNameMode.NameRequired, DirectCall = true, }; // Add the DependsOn property. var dependsOnProp = new DynamicKeywordProperty { Mandatory = true, Name = "DependsOn", }; keywordToAddForThisConfigurationStatement.Properties.Add(dependsOnProp.Name, dependsOnProp); // Add the PsDscRunAsCredential property. var RunAsProp = new DynamicKeywordProperty { Mandatory = true, Name = "PsDscRunAsCredential", }; keywordToAddForThisConfigurationStatement.Properties.Add(RunAsProp.Name, RunAsProp); // Extract the parameters, if any and them to the keyword definition. var sbeAst = configurationBodyScriptBlock as ScriptBlockExpressionAst; if (sbeAst != null) { var pList = sbeAst.ScriptBlock.ParamBlock; if (pList != null) { foreach (var parm in pList.Parameters) { var keywordProp = new DynamicKeywordProperty(); keywordProp.Name = parm.Name.VariablePath.UserPath; if (parm.Attributes != null) { foreach (var attr in parm.Attributes) { var typeConstraint = attr as TypeConstraintAst; if (typeConstraint != null) { keywordProp.TypeConstraint = typeConstraint.TypeName.Name; continue; } var aAst = attr as AttributeAst; if (aAst != null) { if (string.Equals(aAst.TypeName.Name, "Parameter", StringComparison.OrdinalIgnoreCase)) { if (aAst.NamedArguments != null) { foreach (var na in aAst.NamedArguments) { if (string.Equals(na.ArgumentName, "Mandatory", StringComparison.OrdinalIgnoreCase)) { if (na.ExpressionOmitted) { keywordProp.Mandatory = true; } else if (na.Argument != null) { ConstantExpressionAst ceAst = na.Argument as ConstantExpressionAst; if (ceAst != null) { keywordProp.Mandatory = System.Management.Automation.LanguagePrimitives.IsTrue(ceAst.Value); } } } } } } } } } keywordToAddForThisConfigurationStatement.Properties.Add(keywordProp.Name, keywordProp); } } } if (topLevel) { if (_configurationKeywordsDefinedInThisFile == null) { _configurationKeywordsDefinedInThisFile = new Dictionary<string, DynamicKeyword>(); } _configurationKeywordsDefinedInThisFile[keywordToAddForThisConfigurationStatement.Keyword] = keywordToAddForThisConfigurationStatement; } else { System.Management.Automation.Language.DynamicKeyword.AddKeyword(keywordToAddForThisConfigurationStatement); } } #endregion // End of dynamic keyword definition for this function... //############################################################ //############################################################ bool isMetaConfiguration = false; if (customAttributes != null) { isMetaConfiguration = customAttributes.Any( attribute => (attribute.TypeName.GetReflectionAttributeType() != null) && (attribute.TypeName.GetReflectionAttributeType() == typeof(DscLocalConfigurationManagerAttribute))); } ScriptBlockExpressionAst bodyAst = configurationBodyScriptBlock as ScriptBlockExpressionAst; IScriptExtent configurationExtent = ExtentOf(startExtent, bodyAst); return new ConfigurationDefinitionAst(configurationExtent, bodyAst, isMetaConfiguration ? ConfigurationType.Meta : ConfigurationType.Resource, configurationName) { LCurlyToken = lCurly, ConfigurationToken = configurationToken, CustomAttributes = customAttributes, DefinedKeywords = DynamicKeyword.GetKeyword() }; } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); // In theory this should never happen so if it does, we'll report the actual exception rather than introducing a new message ReportError(configurationKeywordToken.Extent, () => "ConfigurationStatementToken: " + e); return null; } finally { // If we had to allocate a runspace for the parser, we'll free it now. if (localRunspace != null) { // If a runspace was created for this operation, close it and reset the default runspace to null. localRunspace.Close(); Runspaces.Runspace.DefaultRunspace = null; } if (topLevel) { // // Clear out all of the cached classes and keywords. // They will need to be reloaded when the generated function is actually run. // Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.ClearCache(); System.Management.Automation.Language.DynamicKeyword.Reset(); } // Finally resync the tokenizer at the current position // this will flush any cached dynamic keyword tokens. var restorePoint = _tokenizer.GetRestorePoint(); Resync(restorePoint); } }