Esempio n. 1
0
        /// <summary>
        /// Routine to format a usage string from keyword. The resulting string should look like:
        ///        User [string] #ResourceName
        ///        {
        ///            UserName = [string]
        ///            [ Description = [string] ]
        ///            [ Disabled = [bool] ]
        ///            [ Ensure = [string] { Absent | Present }  ]
        ///            [ Force = [bool] ]
        ///            [ FullName = [string] ]
        ///            [ Password = [PSCredential] ]
        ///            [ PasswordChangeNotAllowed = [bool] ]
        ///            [ PasswordChangeRequired = [bool] ]
        ///            [ PasswordNeverExpires = [bool] ]
        ///            [ DependsOn = [string[]] ]
        ///        }
        /// </summary>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public static string GetDSCResourceUsageString(DynamicKeyword keyword)
        {
            StringBuilder usageString;
            switch (keyword.NameMode)
            {
                // Name must be present and simple non-empty bare word
                case DynamicKeywordNameMode.SimpleNameRequired:
                    usageString = new StringBuilder(keyword.Keyword + " [string] # Resource Name");
                    break;

                // Name must be present but can also be an expression
                case DynamicKeywordNameMode.NameRequired:
                    usageString = new StringBuilder(keyword.Keyword + " [string[]] # Name List");
                    break;

                // Name may be optionally present, but if it is present, it must be a non-empty bare word.
                case DynamicKeywordNameMode.SimpleOptionalName:
                    usageString = new StringBuilder(keyword.Keyword + " [ [string] ] # Optional Name");
                    break;

                // Name may be optionally present, expression or bare word
                case DynamicKeywordNameMode.OptionalName:
                    usageString = new StringBuilder(keyword.Keyword + " [ [string[]] ] # Optional NameList");
                    break;

                // Does not take a name
                default:
                    usageString = new StringBuilder(keyword.Keyword);
                    break;
            }

            usageString.Append("\n{\n");

            bool listKeyProperties = true;
            while (true)
            {
                foreach (var prop in keyword.Properties.OrderBy(ob => ob.Key))
                {
                    if (string.Equals(prop.Key, "ResourceId", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    var propVal = prop.Value;
                    if (listKeyProperties && propVal.IsKey || !listKeyProperties && !propVal.IsKey)
                    {
                        usageString.Append(propVal.Mandatory ? "    " : "    [ ");
                        usageString.Append(prop.Key);
                        usageString.Append(" = ");
                        usageString.Append(FormatCimPropertyType(propVal, !propVal.Mandatory));
                    }
                }

                if (listKeyProperties)
                {
                    listKeyProperties = false;
                }
                else
                {
                    break;
                }
            }

            usageString.Append("}");

            return usageString.ToString();
        }
Esempio n. 2
0
        /// <summary>
        /// Load the default system CIM classes and create the corresponding keywords.
        /// </summary>
        /// <param name="functionsToDefine">A dictionary to add the defined functions to, may be null</param>
        /// <param name="errors">Collection of any errors encountered while loading keywords.</param>
        /// <param name="modulePathList">List of module path from where DSC PS modules will be loaded</param>
        /// <param name="cacheResourcesFromMultipleModuleVersions">Allow caching the resources from multiple versions of modules</param>
        private static void LoadDefaultCimKeywords(Dictionary<string, ScriptBlock> functionsToDefine, Collection<Exception> errors,
                                                   List<string> modulePathList, bool cacheResourcesFromMultipleModuleVersions)
        {
            DynamicKeyword.Reset();
            Initialize(errors, modulePathList);

            // Initialize->ClearCache resets CacheResourcesFromMultipleModuleVersions to false,
            // workaround is to set it after Initialize method call.
            // Initialize method imports all the Inbox resources and internal classes which belongs to only one version
            // of the module, so it is ok if this property is not set during cache initialization.
            CacheResourcesFromMultipleModuleVersions = cacheResourcesFromMultipleModuleVersions;

            foreach (var cimClass in GetCachedClasses())
            {
                var className = cimClass.Item2.CimSystemProperties.ClassName;
                var moduleInfo = ByClassModuleCache[className];
                CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.Item2, functionsToDefine, cimClass.Item1);
            }

            // And add the Node keyword definitions
            if (!DynamicKeyword.ContainsKeyword("Node"))
            {
                // Implement dispatch to the Node keyword.
                var nodeKeyword = new DynamicKeyword()
                {
                    BodyMode = DynamicKeywordBodyMode.ScriptBlock,
                    ImplementingModule = s_defaultModuleInfoForResource.Item1,
                    ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2,
                    NameMode = DynamicKeywordNameMode.NameRequired,
                    Keyword = "Node",
                };
                DynamicKeyword.AddKeyword(nodeKeyword);
            }

            // And add the Import-DscResource keyword definitions
            if (!DynamicKeyword.ContainsKeyword("Import-DscResource"))
            {
                // Implement dispatch to the Node keyword.
                var nodeKeyword = new DynamicKeyword()
                {
                    BodyMode = DynamicKeywordBodyMode.Command,
                    ImplementingModule = s_defaultModuleInfoForResource.Item1,
                    ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2,
                    NameMode = DynamicKeywordNameMode.NoName,
                    Keyword = "Import-DscResource",
                    MetaStatement = true,
                    PostParse = ImportResourcePostParse,
                    SemanticCheck = ImportResourceCheckSemantics
                };
                DynamicKeyword.AddKeyword(nodeKeyword);
            }
        }
Esempio n. 3
0
        /// <summary>
        /// A method to generate a keyword from a CIM class object. This is used for DSC.
        /// </summary>
        /// <param name="moduleName"></param>
        /// <param name="moduleVersion"></param>
        /// <param name="cimClass"></param>
        /// <param name="functionsToDefine">If true, don't define the keywords, just create the functions.</param>
        /// <param name="runAsBehavior"> To specify RunAs behavior of the class </param>
        private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Version moduleVersion, Microsoft.Management.Infrastructure.CimClass cimClass, Dictionary<string, ScriptBlock> functionsToDefine, DSCResourceRunAsCredential runAsBehavior)
        {
            var resourceName = cimClass.CimSystemProperties.ClassName;
            string alias = GetFriendlyName(cimClass);
            var keywordString = string.IsNullOrEmpty(alias) ? resourceName : alias;

            //
            // Skip all of the base, meta, registration and other classes that are not intended to be used directly by a script author
            //
            if (System.Text.RegularExpressions.Regex.Match(keywordString, "^OMI_Base|^OMI_.*Registration", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success)
            {
                return null;
            }
            var keyword = new DynamicKeyword()
            {
                BodyMode = DynamicKeywordBodyMode.Hashtable,
                Keyword = keywordString,
                ResourceName = resourceName,
                ImplementingModule = moduleName,
                ImplementingModuleVersion = moduleVersion,
                SemanticCheck = CheckMandatoryPropertiesPresent
            };

            // If it's one of reserved dynamic keyword, mark it
            if (System.Text.RegularExpressions.Regex.Match(keywordString, reservedDynamicKeywords, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success)
            {
                keyword.IsReservedKeyword = true;
            }

            // see if it's a resource type i.e. it inherits from OMI_BaseResource
            bool isResourceType = false;
            for (var classToCheck = cimClass; !string.IsNullOrEmpty(classToCheck.CimSuperClassName); classToCheck = classToCheck.CimSuperClass)
            {
                if (string.Equals("OMI_BaseResource", classToCheck.CimSuperClassName, StringComparison.OrdinalIgnoreCase) || string.Equals("OMI_MetaConfigurationResource", classToCheck.CimSuperClassName, StringComparison.OrdinalIgnoreCase))
                {
                    isResourceType = true;
                    break;
                }
            }

            // If it's a resource type, then a resource name is required.
            keyword.NameMode = isResourceType ? DynamicKeywordNameMode.NameRequired : DynamicKeywordNameMode.NoName;

            //
            // Add the settable properties to the keyword object
            //
            foreach (var prop in cimClass.CimClassProperties)
            {
                // If the property is marked as readonly, skip it...
                if ((prop.Flags & Microsoft.Management.Infrastructure.CimFlags.ReadOnly) == Microsoft.Management.Infrastructure.CimFlags.ReadOnly)
                {
                    continue;
                }

                try
                {
                    // If the property has the Read qualifier, also skip it.
                    if (prop.Qualifiers["Read"] != null)
                    {
                        continue;
                    }
                }
                catch (Microsoft.Management.Infrastructure.CimException)
                {
                    // Cim exception means Read wasn't found so continue...
                }

                // If it's one of our magic properties, skip it
                if (IsMagicProperty(prop.Name))
                {
                    continue;
                }

                if (runAsBehavior == DSCResourceRunAsCredential.NotSupported)
                {
                    if (String.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase))
                    {
                        // skip adding PsDscRunAsCredential to the dynamic word for the dsc resource. 
                        continue;
                    }
                }
                // If it's one of our reserved properties, save it for error reporting
                if (System.Text.RegularExpressions.Regex.Match(prop.Name, reservedProperties, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success)
                {
                    keyword.HasReservedProperties = true;
                    continue;
                }

                // Otherwise, add it to the Keyword List.
                var keyProp = new System.Management.Automation.Language.DynamicKeywordProperty();
                keyProp.Name = prop.Name;

                // Set the mandatory flag if appropriate
                if ((prop.Flags & Microsoft.Management.Infrastructure.CimFlags.Key) == Microsoft.Management.Infrastructure.CimFlags.Key)
                {
                    keyProp.Mandatory = true;
                    keyProp.IsKey = true;
                }

                // Copy the type name string. If it's an embedded instance, need to grab it from the ReferenceClassName
                bool referenceClassNameIsNullOrEmpty = string.IsNullOrEmpty(prop.ReferenceClassName);
                if (prop.CimType == CimType.Instance && !referenceClassNameIsNullOrEmpty)
                {
                    keyProp.TypeConstraint = prop.ReferenceClassName;
                }
                else if (prop.CimType == CimType.InstanceArray && !referenceClassNameIsNullOrEmpty)
                {
                    keyProp.TypeConstraint = prop.ReferenceClassName + "[]";
                }
                else
                {
                    keyProp.TypeConstraint = prop.CimType.ToString();
                }

                string[] valueMap = null;
                foreach (var qualifier in prop.Qualifiers)
                {
                    // Check to see if there is a Values attribute and save the list of allowed values if so.
                    if (string.Equals(qualifier.Name, "Values", StringComparison.OrdinalIgnoreCase) && qualifier.CimType == Microsoft.Management.Infrastructure.CimType.StringArray)
                    {
                        keyProp.Values.AddRange((string[])qualifier.Value);
                    }

                    // Check to see if there is a ValueMap attribute and save the list of allowed values if so.
                    if (string.Equals(qualifier.Name, "ValueMap", StringComparison.OrdinalIgnoreCase) && qualifier.CimType == Microsoft.Management.Infrastructure.CimType.StringArray)
                    {
                        valueMap = (string[])qualifier.Value;
                    }

                    // Check to see if this property has the Required qualifier associated with it.
                    if (string.Equals(qualifier.Name, "Required", StringComparison.OrdinalIgnoreCase) &&
                        qualifier.CimType == Microsoft.Management.Infrastructure.CimType.Boolean &&
                            (bool)qualifier.Value)
                    {
                        keyProp.Mandatory = true;
                    }

                    // set the property to mandatory is specified for the resource. 
                    if (runAsBehavior == DSCResourceRunAsCredential.Mandatory)
                    {
                        if (String.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase))
                        {
                            keyProp.Mandatory = true;
                        }
                    }
                }

                if (valueMap != null && keyProp.Values.Count > 0)
                {
                    if (valueMap.Length != keyProp.Values.Count)
                    {
                        s_tracer.WriteLine(
                            "DSC CreateDynamicKeywordFromClass: the count of values for qualifier 'Values' and 'ValueMap' doesn't match. count of 'Values': {0}, count of 'ValueMap': {1}. Skip the keyword '{2}'.",
                            keyProp.Values.Count, valueMap.Length, keyword.Keyword);
                        return null;
                    }

                    for (int index = 0; index < valueMap.Length; index++)
                    {
                        string key = keyProp.Values[index];
                        string value = valueMap[index];

                        if (keyProp.ValueMap.ContainsKey(key))
                        {
                            s_tracer.WriteLine(
                                "DSC CreateDynamicKeywordFromClass: same string value '{0}' appears more than once in qualifier 'Values'. Skip the keyword '{1}'.",
                                key, keyword.Keyword);
                            return null;
                        }

                        keyProp.ValueMap.Add(key, value);
                    }
                }

                keyword.Properties.Add(prop.Name, keyProp);
            }

            // update specific keyword with range constraints
            UpdateKnownRestriction(keyword);

            return keyword;
        }
Esempio n. 4
0
        /// <summary>
        /// update range restriction for meta configuration keywords
        /// the restrictions are for
        /// ConfigurationModeFrequency: 15-44640
        /// RefreshFrequency: 30-44640
        /// </summary>
        /// <param name="keyword"></param>
        private static void UpdateKnownRestriction(DynamicKeyword keyword)
        {
            if (
                string.Compare(keyword.ResourceName, "MSFT_DSCMetaConfigurationV2",
                    StringComparison.OrdinalIgnoreCase) == 0
                ||
                string.Compare(keyword.ResourceName, "MSFT_DSCMetaConfiguration",
                    StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (keyword.Properties["RefreshFrequencyMins"] != null)
                {
                    keyword.Properties["RefreshFrequencyMins"].Range = new Tuple<int, int>(30, 44640);
                }

                if (keyword.Properties["ConfigurationModeFrequencyMins"] != null)
                {
                    keyword.Properties["ConfigurationModeFrequencyMins"].Range = new Tuple<int, int>(15, 44640);
                }

                if (keyword.Properties["DebugMode"] != null)
                {
                    keyword.Properties["DebugMode"].Values.Remove("ResourceScriptBreakAll");
                    keyword.Properties["DebugMode"].ValueMap.Remove("ResourceScriptBreakAll");
                }
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Parse a dynamic keyword statement which will be either of the form
        ///     keyword [parameters] [name] { a=1; b=2; } # constructor with properties
        /// or
        ///     keyword [parameters] [name] { ... }  # constructor with a simple body.
        /// or keywordcommand parameters 
        /// This custom keyword does not introduce a new AST node type. Instead it generates a
        /// CommandAst that calls a PowerShell command to implement the keyword's logic.
        /// This command has one of two signatures:
        ///     keywordImplCommand
        /// </summary>
        /// <param name="functionName">The name of the function to invoke</param>
        /// <param name="keywordData">The data for this keyword definition</param>
        /// <returns></returns>
        private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyword keywordData)
        {
            //////////////////////////////////////////////////////////////////////////////////
            // If a custom action was provided. then invoke it
            //////////////////////////////////////////////////////////////////////////////////
            if (keywordData.PreParse != null)
            {
                try
                {
                    ParseError[] errors = keywordData.PreParse(keywordData);
                    if (errors != null && errors.Length > 0)
                    {
                        foreach (var e in errors)
                        {
                            ReportError(e);
                        }
                    }
                }
                catch (Exception e)
                {
                    ReportError(functionName.Extent, () => ParserStrings.DynamicKeywordPreParseException, keywordData.ResourceName, e.ToString());
                    return null;
                }
            }

            if (keywordData.IsReservedKeyword)
            {
                // ErrorRecovery: eat the token
                ReportError(functionName.Extent, () => ParserStrings.UnsupportedReservedKeyword, keywordData.Keyword);
                return null;
            }

            if (keywordData.HasReservedProperties)
            {
                // ErrorRecovery: eat the token
                ReportError(functionName.Extent, () => ParserStrings.UnsupportedReservedProperty, "'Require', 'Trigger', 'Notify', 'Before', 'After' and 'Subscribe'");
                return null;
            }

            string elementName = string.Empty;

            DynamicKeywordStatementAst dynamicKeywordAst;
            if (keywordData.BodyMode == DynamicKeywordBodyMode.Command)
            {
                UngetToken(functionName);
                dynamicKeywordAst = (DynamicKeywordStatementAst)CommandRule(forDynamicKeyword: true);
                dynamicKeywordAst.Keyword = keywordData;
                dynamicKeywordAst.FunctionName = functionName;
            }
            else
            {
                SkipNewlines();

                // The expression that returns the resource name or names.
                ExpressionAst instanceName = null;

                Token nameToken = NextToken();
                if (nameToken.Kind == TokenKind.EndOfInput)
                {
                    UngetToken(nameToken);

                    if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired)
                    {
                        ReportIncompleteInput(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing);
                    }
                    else
                    {
                        // Name not required so report missing brace
                        ReportIncompleteInput(After(functionName.Extent), () => ParserStrings.MissingBraceInObjectDefinition);
                    }
                    return null;
                }

                // If it's an lcurly, then no name was provided, and we skip to the body processing
                Token lCurly = null;
                if (nameToken.Kind == TokenKind.LCurly)
                {
                    lCurly = nameToken;
                    if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired)
                    {
                        ReportError(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing);
                        UngetToken(nameToken);
                        return null;
                    }
                }
                else if (nameToken.Kind == TokenKind.Identifier || nameToken.Kind == TokenKind.DynamicKeyword)
                {
                    if (keywordData.NameMode == DynamicKeywordNameMode.NoName)
                    {
                        ReportError(After(functionName), () => ParserStrings.UnexpectedNameForType, functionName.Text, nameToken.Text);
                        UngetToken(nameToken);
                        return null;
                    }

                    // If it's an identifier then this is the name for the data object
                    elementName = nameToken.Text;

                    // If only a simple name is allowed, then the string must be non-null.
                    if ((keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName) && string.IsNullOrEmpty(elementName))
                    {
                        ReportIncompleteInput(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing);
                        UngetToken(nameToken);
                        return null;
                    }
                }
                else
                {
                    // see if an expression was provided instead of a bare word...
                    UngetToken(nameToken);
                    instanceName = GetSingleCommandArgument(CommandArgumentContext.CommandName);

                    if (instanceName == null)
                    {
                        if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName)
                        {
                            ReportError(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing);
                        }
                        else
                        {
                            // It wasn't an '{' and it wasn't a name expression so it's a unexpected token.
                            ReportError(After(functionName), () => ParserStrings.UnexpectedToken, nameToken.Text);
                        }
                        return null;
                    }

                    // Ok, we got a name expression, but we're expecting no name, so it's and error.
                    if (keywordData.NameMode == DynamicKeywordNameMode.NoName)
                    {
                        ReportError(After(functionName), () => ParserStrings.UnexpectedNameForType, functionName.Text, instanceName.ToString());
                        return null;
                    }

                    // We were expecting a simple name so report an error
                    if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName)
                    {
                        // If no match, then this is an incomplete token BUGBUG fix message
                        ReportError(nameToken.Extent, () => ParserStrings.UnexpectedToken, nameToken.Text);
                        return null;
                    }
                }

                // If we didn't get a resource expression AST, then we need to build one out of the
                // name that was specified. It may be the case that we don't have
                // a resource name in which case it will be the empty string. Even in the cases were
                // we aren't expecting a name, we still do this so that the signature of the implementing function remains
                // the same.
                ExpressionAst originalInstanceName = instanceName;
                if (instanceName == null)
                {
                    instanceName = new StringConstantExpressionAst(nameToken.Extent, elementName, StringConstantType.BareWord);
                }

                SkipNewlines();

                //
                // Now look for the body of the data statement.
                //
                if (lCurly == null)
                {
                    lCurly = NextToken();

                    if (lCurly.Kind == TokenKind.EndOfInput)
                    {
                        UngetToken(lCurly);
                        ReportIncompleteInput(After(functionName.Extent), () => ParserStrings.MissingBraceInObjectDefinition);

                        // Preserve the name expression for tab completion
                        return originalInstanceName == null
                                   ? null
                                   : new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
                                                           GetNestedErrorAsts(originalInstanceName));
                    }

                    if (lCurly.Kind != TokenKind.LCurly)
                    {
                        // We need to generate a reasonable error message for this case:
                        //
                        // Configuration C {
                        //   node $AllNode.NodeName{ # There is no space before curly, and we are converting scriptblock to and argument to call 'NodeName'
                        //     ...
                        //   }
                        // } # we don't want to simple report an unexpected token here, it would be super-confusing.

                        InvokeMemberExpressionAst instanceInvokeMemberExpressionAst = instanceName as InvokeMemberExpressionAst;

                        if (instanceInvokeMemberExpressionAst != null &&
                            instanceInvokeMemberExpressionAst.Arguments.Count == 1 &&
                            instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst &&
                            // the last condition checks that there is no space between "method" name and '{'
                            instanceInvokeMemberExpressionAst.Member.Extent.EndOffset == instanceInvokeMemberExpressionAst.Arguments[0].Extent.StartOffset)
                        {
                            ReportError(LastCharacterOf(instanceInvokeMemberExpressionAst.Member.Extent), () => ParserStrings.UnexpectedTokenInDynamicKeyword, functionName.Text);
                        }
                        else
                        {
                            ReportError(lCurly.Extent, () => ParserStrings.UnexpectedToken, lCurly.Text);
                        }

                        if (lCurly.Kind == TokenKind.Dot && originalInstanceName != null && lCurly.Extent.StartOffset == originalInstanceName.Extent.EndOffset)
                        {
                            // Generate more useful ast for tab-completing extension methods on special DSC collection variables
                            // e.g. configuration foo { node $AllNodes.<tab>

                            IScriptExtent errorExtent = ExtentOf(originalInstanceName, lCurly);
                            var errorExpr = new ErrorExpressionAst(errorExtent);
                            var memberExpr = new MemberExpressionAst(originalInstanceName.Extent, originalInstanceName, errorExpr, @static: false);

                            return new ErrorStatementAst(errorExtent, new[] { memberExpr });
                        }

                        UngetToken(lCurly);
                        // Preserve the name expression for tab completion
                        return originalInstanceName == null
                                   ? null
                                   : new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
                                                           GetNestedErrorAsts(originalInstanceName));
                    }
                }

                //
                // The keyword data is used to see
                // if a scriptblock or a hashtable is expected.
                //
                ExpressionAst body = null;
                if (keywordData.BodyMode == DynamicKeywordBodyMode.ScriptBlock)
                {
                    var oldInConfiguraiton = _inConfiguration;
                    try
                    {
                        _inConfiguration = false;
                        body = ScriptBlockExpressionRule(lCurly);
                    }
                    finally
                    {
                        _inConfiguration = oldInConfiguraiton;
                    }
                }
                else if (keywordData.BodyMode == DynamicKeywordBodyMode.Hashtable)
                {
                    // Resource property value could be set to nested DSC resources except Script resource
                    bool isScriptResource = String.Compare(functionName.Text, @"Script", StringComparison.OrdinalIgnoreCase) == 0;
                    try
                    {
                        if (isScriptResource)
                            DynamicKeyword.Push();
                        body = HashExpressionRule(lCurly, true /* parsingSchemaElement */);
                    }
                    finally
                    {
                        if (isScriptResource)
                            DynamicKeyword.Pop();
                    }
                }
                // commandast
                // elements: instancename/dynamickeyword/hashtable or scripblockexpress
                if (body == null)
                {
                    // Failed to read the statement body
                    ReportIncompleteInput(After(lCurly), () => ParserStrings.MissingStatementAfterKeyword, keywordData.Keyword);

                    // Preserve the name expression for tab completion
                    return originalInstanceName == null
                               ? null
                               : new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
                                                       GetNestedErrorAsts(originalInstanceName));
                }

                //////////////////////////////////////////////////////////////////////////
                // The statement is now fully parsed
                //////////////////////////////////////////////////////////////////////////

                //
                // Create DynamicKeywordStatementAst
                //
                Collection<CommandElementAst> commandElements = new Collection<CommandElementAst>
                {
                    new StringConstantExpressionAst(functionName.Extent, functionName.Text, StringConstantType.BareWord),
                    (ExpressionAst)instanceName.Copy(),
                    (ExpressionAst)body.Copy()
                };
                Token nextToken = NextToken();
                IScriptExtent dynamicKeywordExtent = ExtentOf(functionName, Before(nextToken));
                UngetToken(nextToken);
                dynamicKeywordAst = new DynamicKeywordStatementAst(dynamicKeywordExtent, commandElements)
                {
                    Keyword = keywordData,
                    LCurly = lCurly,
                    FunctionName = functionName,
                    InstanceName = instanceName,
                    OriginalInstanceName = originalInstanceName,
                    BodyExpression = body,
                    ElementName = elementName,
                };
            }

            //////////////////////////////////////////////////////////////////////////////////
            // If a custom action was provided. then invoke it
            //////////////////////////////////////////////////////////////////////////////////
            if (keywordData.PostParse != null)
            {
                try
                {
                    ParseError[] errors = keywordData.PostParse(dynamicKeywordAst);
                    if (errors != null && errors.Length > 0)
                    {
                        foreach (var e in errors)
                        {
                            ReportError(e);
                        }
                    }
                }
                catch (Exception e)
                {
                    ReportError(functionName.Extent, () => ParserStrings.DynamicKeywordPostParseException, keywordData.Keyword, e.ToString());
                    return null;
                }
            }

            return dynamicKeywordAst;
        }