@@ -1398,85 +1398,97 @@ private static bool TryReadPSDataFile(
13981398 }
13991399 string contents = System . IO . File . ReadAllText ( filePath ) ;
14001400
1401+ // Validate that the file content conforms to restricted language rules before execution.
1402+ // This parses the content into an AST and statically validates it only contains data-file-safe constructs (hashtables, arrays, literals, etc).
1403+ // It throws a ParseException if anything disallowed is found, before any code is run.
1404+ ScriptBlock scriptBlock = ScriptBlock . Create ( contents ) ;
1405+ scriptBlock . CheckRestrictedLanguage ( allowedCommands , allowedVariables , allowEnvironmentVariables ) ;
1406+
14011407 // Parallel.ForEach calls into this method.
14021408 // Each thread needs its own runspace created to provide a separate environment for operations to run independently.
1403- Runspace runspace = RunspaceFactory . CreateRunspace ( ) ;
1404- runspace . Open ( ) ;
1405- runspace . SessionStateProxy . LanguageMode = PSLanguageMode . ConstrainedLanguage ;
1406-
1407- // Save and set the default runspace for the current thread to prevent
1408- // stale DefaultRunspace from a prior operation on this reused thread-pool thread.
1409- Runspace previousDefaultRunspace = Runspace . DefaultRunspace ;
1410- try
1409+ using ( Runspace runspace = RunspaceFactory . CreateRunspace ( ) )
14111410 {
1412- Runspace . DefaultRunspace = runspace ;
1411+ runspace . Open ( ) ;
1412+ runspace . SessionStateProxy . LanguageMode = PSLanguageMode . ConstrainedLanguage ;
14131413
1414- using ( System . Management . Automation . PowerShell pwsh = System . Management . Automation . PowerShell . Create ( ) )
1414+ // Save and set the default runspace for the current thread to prevent
1415+ // stale DefaultRunspace from a prior operation on this reused thread-pool thread.
1416+ Runspace previousDefaultRunspace = Runspace . DefaultRunspace ;
1417+ try
14151418 {
1416- pwsh . Runspace = runspace ;
1419+ Runspace . DefaultRunspace = runspace ;
14171420
1418- var cmd = new Command (
1419- command : contents ,
1420- isScript : true ,
1421- useLocalScope : true ) ;
1422- cmd . MergeMyResults (
1423- myResult : PipelineResultTypes . Error | PipelineResultTypes . Warning | PipelineResultTypes . Verbose | PipelineResultTypes . Debug | PipelineResultTypes . Information ,
1424- toResult : PipelineResultTypes . Output ) ;
1425- pwsh . Commands . AddCommand ( cmd ) ;
1426-
1427- try
1421+ using ( System . Management . Automation . PowerShell pwsh = System . Management . Automation . PowerShell . Create ( ) )
14281422 {
1429- // Invoke the pipeline and retrieve the results
1430- var results = pwsh . Invoke ( ) ;
1431-
1432- if ( results [ 0 ] is PSObject pwshObj )
1423+ pwsh . Runspace = runspace ;
1424+
1425+ var cmd = new Command (
1426+ command : contents ,
1427+ isScript : true ,
1428+ useLocalScope : true ) ;
1429+ cmd . MergeMyResults (
1430+ myResult : PipelineResultTypes . Error | PipelineResultTypes . Warning | PipelineResultTypes . Verbose | PipelineResultTypes . Debug | PipelineResultTypes . Information ,
1431+ toResult : PipelineResultTypes . Output ) ;
1432+ pwsh . Commands . AddCommand ( cmd ) ;
1433+
1434+ try
14331435 {
1434- switch ( pwshObj . BaseObject )
1435- {
1436- case ErrorRecord err :
1437- //_cmdletPassedIn.WriteError(error);
1438- break ;
1439-
1440- case WarningRecord warning :
1441- //cmdlet.WriteWarning(warning.Message);
1442- break ;
1443-
1444- case VerboseRecord verbose :
1445- //cmdlet.WriteVerbose(verbose.Message);
1446- break ;
1436+ // Invoke the pipeline and retrieve the results
1437+ var results = pwsh . Invoke ( ) ;
14471438
1448- case DebugRecord debug :
1449- //cmdlet.WriteDebug(debug.Message);
1450- break ;
1451-
1452- case InformationRecord info :
1453- //cmdlet.WriteInformation(info);
1454- break ;
1455-
1456- case Hashtable result :
1457- dataFileInfo = result ;
1458- return true ;
1439+ if ( results [ 0 ] is PSObject pwshObj )
1440+ {
1441+ switch ( pwshObj . BaseObject )
1442+ {
1443+ case ErrorRecord err :
1444+ //_cmdletPassedIn.WriteError(error);
1445+ break ;
1446+
1447+ case WarningRecord warning :
1448+ //cmdlet.WriteWarning(warning.Message);
1449+ break ;
1450+
1451+ case VerboseRecord verbose :
1452+ //cmdlet.WriteVerbose(verbose.Message);
1453+ break ;
1454+
1455+ case DebugRecord debug :
1456+ //cmdlet.WriteDebug(debug.Message);
1457+ break ;
1458+
1459+ case InformationRecord info :
1460+ //cmdlet.WriteInformation(info);
1461+ break ;
1462+
1463+ case Hashtable result :
1464+ dataFileInfo = result ;
1465+ return true ;
1466+ }
14591467 }
14601468 }
1469+ catch ( Exception ex )
1470+ {
1471+ error = ex ;
1472+ }
14611473 }
1462- catch ( Exception ex )
1463- {
1464- error = ex ;
1465- }
1474+ // Return false to indicate "we couldn't parse a valid Hashtable from this .psd1 file."
1475+ // The only success path is the 'case Hashtable result' branch above which does return true.
1476+ return false ;
1477+ }
1478+ finally
1479+ {
1480+ // Always restore the previous default runspace for the current thread.
1481+ Runspace . DefaultRunspace = previousDefaultRunspace ;
14661482 }
1467- // Return false to indicate "we couldn't parse a valid Hashtable from this .psd1 file."
1468- // The only success path is the 'case Hashtable result' branch above which does return true.
1469- return false ;
1470- }
1471- finally
1472- {
1473- // Always restore the previous default runspace and close/dispose
1474- // the per-thread runspace, even on success (return true) or exception paths.
1475- Runspace . DefaultRunspace = previousDefaultRunspace ;
1476- runspace . Close ( ) ;
1477- runspace . Dispose ( ) ;
14781483 }
14791484 }
1485+ catch ( System . Management . Automation . ParseException parseEx )
1486+ {
1487+ error = new InvalidDataException (
1488+ $ "The file '{ filePath } ' cannot be parsed as a PowerShell data file. It contains disallowed language elements: { parseEx . Message } ",
1489+ parseEx ) ;
1490+ return false ;
1491+ }
14801492 catch ( Exception ex )
14811493 {
14821494 error = ex ;
0 commit comments