379 lines
16 KiB
C#
379 lines
16 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Management.Automation;
|
|
using System.Text;
|
|
|
|
using Microsoft.Management.Infrastructure;
|
|
using Microsoft.PowerShell.Cim;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace Microsoft.PowerShell.Cmdletization.Cim
|
|
{
|
|
/// <summary>
|
|
/// CimQuery supports building of queries against CIM object model.
|
|
/// </summary>
|
|
internal class CimQuery : QueryBuilder, ISessionBoundQueryBuilder<CimSession>
|
|
{
|
|
private readonly StringBuilder _wqlCondition;
|
|
|
|
private CimInstance _associatedObject;
|
|
private string _associationName;
|
|
private string _resultRole;
|
|
private string _sourceRole;
|
|
|
|
internal readonly Dictionary<string, object> queryOptions = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
internal ClientSideQuery ClientSideQuery { get; }
|
|
|
|
internal CimQuery()
|
|
{
|
|
_wqlCondition = new StringBuilder();
|
|
this.ClientSideQuery = new ClientSideQuery();
|
|
}
|
|
|
|
#region WQL processing
|
|
|
|
private void AddWqlCondition(string condition)
|
|
{
|
|
_wqlCondition.Append(_wqlCondition.Length != 0 ? " AND " : " WHERE ");
|
|
|
|
_wqlCondition.Append('(');
|
|
_wqlCondition.Append(condition);
|
|
_wqlCondition.Append(')');
|
|
}
|
|
|
|
private static string ObjectToWqlLiteral(object o)
|
|
{
|
|
if (LanguagePrimitives.IsNull(o))
|
|
{
|
|
return "null"; // based on an example at https://msdn.microsoft.com/library/aa394054(VS.85).aspx
|
|
}
|
|
|
|
o = CimValueConverter.ConvertFromDotNetToCim(o);
|
|
PSObject pso = PSObject.AsPSObject(o);
|
|
Type type = pso.BaseObject.GetType();
|
|
TypeCode typeCode = LanguagePrimitives.GetTypeCode(type);
|
|
|
|
if (typeCode == TypeCode.String)
|
|
{
|
|
string s = o.ToString();
|
|
s = s.Replace("\\", "\\\\");
|
|
s = s.Replace("'", "\\'");
|
|
return "'" + s + "'";
|
|
}
|
|
|
|
if (typeCode == TypeCode.Char)
|
|
{
|
|
return ObjectToWqlLiteral(LanguagePrimitives.ConvertTo(o, typeof(string), CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
if (typeCode == TypeCode.DateTime)
|
|
{
|
|
var dateTime = (DateTime)LanguagePrimitives.ConvertTo(o, typeof(DateTime), CultureInfo.InvariantCulture);
|
|
string result = ClrFacade.ToDmtfDateTime(dateTime);
|
|
return "'" + result + "'";
|
|
}
|
|
|
|
if (type == typeof(TimeSpan))
|
|
{
|
|
// WMIv1 does not support using interval literals in a WQL query
|
|
return null;
|
|
}
|
|
|
|
if (LanguagePrimitives.IsNumeric(typeCode))
|
|
{
|
|
return (string)LanguagePrimitives.ConvertTo(o, typeof(string), CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
if (LanguagePrimitives.IsBooleanType(type))
|
|
{
|
|
if ((bool)LanguagePrimitives.ConvertTo(o, typeof(bool), CultureInfo.InvariantCulture))
|
|
{
|
|
return "TRUE"; // based on https://msdn.microsoft.com/library/aa394054(VS.85).aspx
|
|
}
|
|
|
|
return "FALSE"; // based on https://msdn.microsoft.com/library/aa394054(VS.85).aspx
|
|
}
|
|
|
|
throw CimValueConverter.GetInvalidCastException(
|
|
null, /* inner exception */
|
|
"InvalidCimQueryCast",
|
|
o,
|
|
CmdletizationResources.CimConversion_WqlQuery);
|
|
}
|
|
|
|
private static string WildcardToWqlLikeOperand(WildcardPattern wildcardPattern, out bool needsClientSideFiltering)
|
|
{
|
|
string nakedOperand = WildcardPatternToCimQueryParser.Parse(wildcardPattern, out needsClientSideFiltering);
|
|
|
|
return ObjectToWqlLiteral(nakedOperand);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static string GetMatchConditionForEqualityOperator(string propertyName, object propertyValue)
|
|
{
|
|
string condition;
|
|
|
|
// comparison of 'char' is case-sensitive in WQL (comparison of 'string' is case-insensitive)
|
|
if (propertyValue is char)
|
|
{
|
|
char c = (char)propertyValue;
|
|
char lowerCase = char.ToLowerInvariant(c);
|
|
char upperCase = char.ToUpperInvariant(c);
|
|
string lowerCaseLiteral = CimQuery.ObjectToWqlLiteral(lowerCase);
|
|
string upperCaseLiteral = CimQuery.ObjectToWqlLiteral(upperCase);
|
|
Dbg.Assert(!string.IsNullOrWhiteSpace(lowerCaseLiteral), "All characters are assumed to have valid WQL literals (lower)");
|
|
Dbg.Assert(!string.IsNullOrWhiteSpace(upperCaseLiteral), "All characters are assumed to have valid WQL literals (upper)");
|
|
condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"(({0} = {1}) OR ({0} = {2}))",
|
|
propertyName,
|
|
lowerCaseLiteral,
|
|
upperCaseLiteral);
|
|
return condition;
|
|
}
|
|
|
|
string wqlLiteral = CimQuery.ObjectToWqlLiteral(propertyValue);
|
|
if (string.IsNullOrWhiteSpace(wqlLiteral))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"({0} = {1})",
|
|
propertyName,
|
|
wqlLiteral);
|
|
return condition;
|
|
}
|
|
|
|
private static string GetMatchConditionForLikeOperator(string propertyName, object propertyValue)
|
|
{
|
|
var expectedPropertyValueAsString = (string)LanguagePrimitives.ConvertTo(propertyValue, typeof(string), CultureInfo.InvariantCulture);
|
|
var expectedPropertyValueAsPowerShellWildcard = WildcardPattern.Get(expectedPropertyValueAsString, WildcardOptions.IgnoreCase);
|
|
|
|
bool needsClientSideFiltering; // not used because for simplicity all results go through post-filtering
|
|
var expectedPropertyValueAsWqlWildcard = CimQuery.WildcardToWqlLikeOperand(expectedPropertyValueAsPowerShellWildcard, out needsClientSideFiltering);
|
|
|
|
string condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"({0} LIKE {1})",
|
|
propertyName,
|
|
expectedPropertyValueAsWqlWildcard);
|
|
return condition;
|
|
}
|
|
|
|
private static string GetMatchCondition(string propertyName, IEnumerable propertyValues, bool wildcardsEnabled)
|
|
{
|
|
List<string> individualConditions = propertyValues
|
|
.Cast<object>()
|
|
.Select(propertyValue => wildcardsEnabled
|
|
? GetMatchConditionForLikeOperator(propertyName, propertyValue)
|
|
: GetMatchConditionForEqualityOperator(propertyName, propertyValue))
|
|
.Where(static individualCondition => !string.IsNullOrWhiteSpace(individualCondition))
|
|
.ToList();
|
|
if (individualConditions.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string result = string.Join(" OR ", individualConditions);
|
|
return result;
|
|
}
|
|
|
|
#region Public inputs from cmdletization
|
|
|
|
/// <summary>
|
|
/// Modifies the query, so that it only returns objects with a given property value.
|
|
/// </summary>
|
|
/// <param name="propertyName">Property name to query on.</param>
|
|
/// <param name="allowedPropertyValues">Property values to accept in the query.</param>
|
|
/// <param name="wildcardsEnabled">
|
|
/// <see langword="true"/> if <paramref name="allowedPropertyValues"/> should be treated as a <see cref="string"/> containing a wildcard pattern;
|
|
/// <see langword="false"/> otherwise.
|
|
/// </param>
|
|
/// <param name="behaviorOnNoMatch">
|
|
/// Describes how to handle filters that didn't match any objects
|
|
/// </param>
|
|
public override void FilterByProperty(string propertyName, IEnumerable allowedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch)
|
|
{
|
|
this.ClientSideQuery.FilterByProperty(propertyName, allowedPropertyValues, wildcardsEnabled, behaviorOnNoMatch);
|
|
|
|
string matchCondition = CimQuery.GetMatchCondition(propertyName, allowedPropertyValues, wildcardsEnabled);
|
|
if (!string.IsNullOrWhiteSpace(matchCondition))
|
|
{
|
|
this.AddWqlCondition(matchCondition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modifies the query, so that it does not return objects with a given property value.
|
|
/// </summary>
|
|
/// <param name="propertyName">Property name to query on.</param>
|
|
/// <param name="excludedPropertyValues">Property values to reject in the query.</param>
|
|
/// <param name="wildcardsEnabled">
|
|
/// <see langword="true"/> if <paramref name="excludedPropertyValues"/> should be treated as a <see cref="string"/> containing a wildcard pattern;
|
|
/// <see langword="false"/> otherwise.
|
|
/// </param>
|
|
/// <param name="behaviorOnNoMatch">
|
|
/// Describes how to handle filters that didn't match any objects
|
|
/// </param>
|
|
public override void ExcludeByProperty(string propertyName, IEnumerable excludedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch)
|
|
{
|
|
this.ClientSideQuery.ExcludeByProperty(propertyName, excludedPropertyValues, wildcardsEnabled, behaviorOnNoMatch);
|
|
|
|
string positiveWqlCondition = CimQuery.GetMatchCondition(propertyName, excludedPropertyValues, wildcardsEnabled);
|
|
if (!string.IsNullOrWhiteSpace(positiveWqlCondition))
|
|
{
|
|
string condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"NOT ({0})",
|
|
positiveWqlCondition);
|
|
this.AddWqlCondition(condition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modifies the query, so that it returns only objects that have a property value greater than or equal to a <paramref name="minPropertyValue"/> threshold.
|
|
/// </summary>
|
|
/// <param name="propertyName">Property name to query on.</param>
|
|
/// <param name="minPropertyValue">Minimum property value.</param>
|
|
/// <param name="behaviorOnNoMatch">
|
|
/// Describes how to handle filters that didn't match any objects
|
|
/// </param>
|
|
public override void FilterByMinPropertyValue(string propertyName, object minPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch)
|
|
{
|
|
this.ClientSideQuery.FilterByMinPropertyValue(propertyName, minPropertyValue, behaviorOnNoMatch);
|
|
|
|
string wqlLiteral = CimQuery.ObjectToWqlLiteral(minPropertyValue);
|
|
if (!string.IsNullOrWhiteSpace(wqlLiteral))
|
|
{
|
|
string condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"{0} >= {1}",
|
|
propertyName,
|
|
wqlLiteral);
|
|
this.AddWqlCondition(condition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modifies the query, so that it returns only objects that have a property value less than or equal to a <paramref name="maxPropertyValue"/> threshold.
|
|
/// </summary>
|
|
/// <param name="propertyName">Property name to query on.</param>
|
|
/// <param name="maxPropertyValue">Maximum property value.</param>
|
|
/// <param name="behaviorOnNoMatch">
|
|
/// Describes how to handle filters that didn't match any objects
|
|
/// </param>
|
|
public override void FilterByMaxPropertyValue(string propertyName, object maxPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch)
|
|
{
|
|
this.ClientSideQuery.FilterByMaxPropertyValue(propertyName, maxPropertyValue, behaviorOnNoMatch);
|
|
|
|
string wqlLiteral = CimQuery.ObjectToWqlLiteral(maxPropertyValue);
|
|
if (!string.IsNullOrWhiteSpace(wqlLiteral))
|
|
{
|
|
string condition = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"{0} <= {1}",
|
|
propertyName,
|
|
CimQuery.ObjectToWqlLiteral(maxPropertyValue));
|
|
this.AddWqlCondition(condition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modifies the query, so that it returns only objects associated with <paramref name="associatedInstance"/>
|
|
/// </summary>
|
|
/// <param name="associatedInstance">Object that query results have to be associated with.</param>
|
|
/// <param name="associationName">Name of the association.</param>
|
|
/// <param name="resultRole">Name of the role that <paramref name="associatedInstance"/> has in the association.</param>
|
|
/// <param name="sourceRole">Name of the role that query results have in the association.</param>
|
|
/// <param name="behaviorOnNoMatch">
|
|
/// Describes how to handle filters that didn't match any objects
|
|
/// </param>
|
|
public override void FilterByAssociatedInstance(object associatedInstance, string associationName, string sourceRole, string resultRole, BehaviorOnNoMatch behaviorOnNoMatch)
|
|
{
|
|
this.ClientSideQuery.FilterByAssociatedInstance(associatedInstance, associationName, sourceRole, resultRole, behaviorOnNoMatch);
|
|
_associatedObject = associatedInstance as CimInstance;
|
|
_associationName = associationName;
|
|
_resultRole = resultRole;
|
|
_sourceRole = sourceRole;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a query option.
|
|
/// </summary>
|
|
/// <param name="optionName"></param>
|
|
/// <param name="optionValue"></param>
|
|
public override void AddQueryOption(string optionName, object optionValue)
|
|
{
|
|
if (string.IsNullOrEmpty(optionName))
|
|
{
|
|
throw new ArgumentNullException(nameof(optionName));
|
|
}
|
|
|
|
if (optionValue == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(optionValue));
|
|
}
|
|
|
|
this.queryOptions[optionName] = optionValue;
|
|
}
|
|
|
|
#endregion Cmdletization inputs
|
|
|
|
#region Outputs for doing the query
|
|
|
|
internal StartableJob GetQueryJob(CimJobContext jobContext)
|
|
{
|
|
if (_associationName == null)
|
|
{
|
|
return new QueryInstancesJob(jobContext, this, _wqlCondition.ToString());
|
|
}
|
|
else
|
|
{
|
|
return new EnumerateAssociatedInstancesJob(jobContext, this, _associatedObject, _associationName, _resultRole, _sourceRole);
|
|
}
|
|
}
|
|
|
|
internal bool IsMatchingResult(CimInstance result)
|
|
{
|
|
Dbg.Assert(result != null, "Caller should verify result != null");
|
|
return this.ClientSideQuery.IsResultMatchingClientSideQuery(result);
|
|
}
|
|
|
|
internal IEnumerable<ClientSideQuery.NotFoundError> GenerateNotFoundErrors()
|
|
{
|
|
return this.ClientSideQuery.GenerateNotFoundErrors();
|
|
}
|
|
|
|
#endregion
|
|
|
|
CimSession ISessionBoundQueryBuilder<CimSession>.GetTargetSession()
|
|
{
|
|
if (_associatedObject != null)
|
|
{
|
|
return CimCmdletAdapter.GetSessionOfOriginFromCimInstance(_associatedObject);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string that represents the current CIM query.
|
|
/// </summary>
|
|
/// <returns>A string that represents the current CIM query.</returns>
|
|
public override string ToString()
|
|
{
|
|
return _wqlCondition.ToString();
|
|
}
|
|
}
|
|
}
|