Why Error "System.String does not contain a method named 'AppendChild'" - powershell

Why does this PowerShell Script not work:
[xml]$xml = '<products></products>'
$newproduct = $xml.CreateElement('product')
$attr = $xml.CreateAttribute('code')
$attr.Value = 'id1'
$newproduct.Attributes.Append($attr)
$products = $xml.products
$products.AppendChild($newproduct)
Error is
Method invocation failed because [System.String] does not contain a method named 'AppendChild'.
At line:1 char:1
+ $products.AppendChild($newproduct)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
If I replace
$products = $xml.products
by
$products = $xml.SelectSingleNode('//products')
it will work, but I'd like to know why first syntax does not work because it is illogical for me. $xml.products should be a valid XML object and thus provide the method AppendChild().

$xml.products does not reference the products node itself, but the contents of the products node. Since products is empty, it evaluates to an empty string.
To get to the products node you could also use:
$Products = $xml.FirstChild
$Products.AppendChild($newproduct)
or, more specifically:
$Products = $xml.ChildNodes.Where({$_.Name -eq "products"}) |Select-Object -First 1
$Products.AppendChild($newproduct)
But SelectSingleNode() will probably serve you just fine

$xml.configuration.SelectSingleNode("connectionStrings").AppendChild($newnode)
would work fine, but an interesting & dirty hack: given this empty node:
<connectionStrings>
</connectionStrings>
the script
$xml.configuration.connectionStrings.AppendChild($newnode)
gives a "Method invocation failed because [System.String] does not contain a method named 'AppendChild'" error
but this:
<connectionStrings>
<!-- random comment -->
</connectionStrings>
works perfectly fine

Related

A positional parameter cannot be found that accepts argument '+' error using SqlClient in Powershell

I am getting an error in my powershell script for a runbook on Azure:
Write-Error : A positional parameter cannot be found that accepts argument '+'.
At Test-Update-IndexesForallShardsFromShardManagerRunbook:61 char:61
+
+ CategoryInfo : InvalidArgument: (:) [Write-Error], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.WriteErrorCommand
Based on the logs I see on my Azure autmation account from a job that ran, I pinpointed the origin of the error in my script somewhere in the following code:
$updateStatisticSql = "UPDATE STATISTICS [$Using:tableName] ( [$Using:statName] );"
$CmdUpdateStats=new-object system.Data.SqlClient.SqlCommand($updateStatisticSql, $Conn1)
$CmdUpdateStats.CommandTimeout=1500
Try
{
$Ds=New-Object system.Data.DataSet
$Da=New-Object system.Data.SqlClient.SqlDataAdapter($CmdUpdateStats)
[void]$Da.fill($Ds)
}
Catch
{
# Will catch the exception here so other statistics can be processed.
Write-Error "Statistic " + $tableName + "(" + $statName + ") could not be updated. Investigate the statistic."
}
It seems after adding logging after each line, that it doesn't log after the "fill" function, so I assume something is going wrong there. But I am not seeing the relation between the error and this function. It also doesn't seem a script breaking error, since it never goes into the catch and the rest of the scripts runs fine. I also validated that the statistics are updated, even though the error I am getting.
So the error you are seeing is because you are trying to build a string using concatenation which means you have spaces and spaces are used to delimit parameters when calling cmdlets. Put all the concatenation into parens:
Write-Error ("Statistic " + $tableName + "(" + $statName + ") could not be updated. Investigate the statistic.")

Override Creation for Monitors and Rules

I want to create PowerShell scripts to override some parameter of my monitor and rule. I used the below code, but I have some errors. I want override my overidable parameter not enabled or something else. How can I doe this?
$mps = Get-SCOMManagementPack | ? {$_.Name -like "test"}
$overrideMp = Get-SCOMManagementPack -DisplayName "Overrides"
$overridename = "testmonitor.Overrides"
$monitor = 'testmonitor'
$override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackMonitorPropertyOverride($overrideMp,$overridename)
$override.Monitor = $monitor
$override.Property = 'WarningThreshold'
$override.Value = 80
$override.DisplayName = "Overrides"
$overrideMp.Verify()
$overrideMp.AcceptChanges()
Errors:
error1: Exception setting "Property": "Cannot convert value "WarningThreshold" to
type "Microsoft.EnterpriseManagement.Configuration.ManagementPackMonitorProperty".
Error: "Unable to match the identifier name WarningThreshold to a valid enumerator
name. Specify one of the following enumerator names and try again: Enabled,
TraceEnabled, Algorithm, AlgorithmPercentage, DefaultState, GenerateAlert,
AutoResolve, AlertPriority, AlertOnState, AlertSeverity, AlertMessage,
AlertParameter1, AlertParameter2, AlertParameter3, AlertParameter4,
AlertParameter5, AlertParameter6, AlertParameter7, AlertParameter8,
AlertParameter9, AlertParameter10, MemberInMaintenance, MemberUnavailable,
IgnoreMemberInMaintenance, IgnoreMemberUnavailable""
At line:1 char:2
+ $override.Property = $parametername
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
error2 : Exception calling "AcceptChanges" with "0" argument(s): "Database error.
MPInfra_p_ManagementPackInstall failed with exception: Failed to validate item:
testrule1"
At line:193 char:1
+ $MP.AcceptChanges()
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ManagementPackException
The error message looks rather clear to me. There is no property WarningThreshold in the ManagementPackMonitorProperty enumeration. I don't have much experience with SCOM, but you probably need to override the property AlertOnState for monitors where the property AlertSeverity has the value Warning.
Try something along the lines of this:
$mps | Get-SCOMMonitor | Where-Object {
# (other selection criteria) -and
$_.AlertSettings.AlertSeverity -eq 'Warning'
} | ForEach-Object {
$ctx = Get-SCOMClass -Id $_.Target.Id
# ...
$override = New-Object ...
$override.Monitor = $_
$override.Property = 'AlertOnState'
$override.Value = 80
$override.Context = $ctx
# ...
}
Code adopted from here (probably the same place where you found it). Not sure if this works, though. Like I said, I have very little experience with SCOM, and I don't have a SCOM server available for testing.
I'll try to debug it tomorrow in the office.
BTW, there is a third-party tool for override management called MPTuner: http://mpwiki.viacode.com/default.aspx?g=mptuner
It's free so you should try.
Roman.
It's quite confusing, but there are two different type of overrides for each workflow type. For a monitor there are:
MonitorPropertyOverride
MonitorConfigurationOverride
You are using the first one, wgich is for standard parameters only, like Enabled, for example. For any custom parameters use Configuration Override.

PowerShell InvokeGet the directory property cannot be found

We needed to retrieve the information in active directory concerning 'Terminal Services'. For this I've created a function that works fine most of the time. However, with some users we have issues.
The code:
Function Get-ADTSProfile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[String] $DistinguishedName,
[parameter(Mandatory=$true,Position=1)]
[ValidateNotNullOrEmpty()]
[ValidateSet('UserProfile','AllowLogon','HomeDirectory','HomeDrive')]
[String]$Property
)
Begin {
$User = [ADSI]"LDAP://$DistinguishedName"
}
Process {
Switch ($Property) {
'AllowLogon' {if ($($User.psbase.InvokeGet('allowLogon')) -eq '1'){$True}else{$False}}
'HomeDirectory' {$User.psbase.InvokeGet('TerminalServicesHomeDirectory')}
'HomeDrive' {$User.psbase.InvokeGet('TerminalServicesHomeDrive')}
'UserProfile' {$User.psbase.InvokeGet('TerminalServicesProfilePath')}
}
}
}
The error:
Get-ADTSProfile -DistinguishedName 'CN=test\, test (Den Bosch) NLD,OU=Users,OU=Disabled,OU=NLD,OU=EU,DC=domain,DC=net' -Property 'UserProfile'
Exception calling "InvokeGet" with "1" argument(s): "The directory property cannot be fo
und in the cache.
"
At S:\Test\Brecht\Testie.ps1:84 char:38
+ 'UserProfile' {$User.psbase.InvokeGet('TerminalServicesPro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodTargetInvocation
I can't really figure out why it works on some and not on all..
I've been working on a recent project that uses ADSI to set and read Terminal Services attributes. From my testing anytime you perform a "InvokeGet({TS Attribute})" a COM exception will be thrown with the message "The directory property cannot be found in cache"
This seems to occur only when the "userParameters" attribute is not set in AD. Maybe the attribute internally checks the ADSI cache for userParameters? So i'm thinking logically you could check the DirectoryEntry for userParameters first, then try and read the properties, or else set it to construct the blob
if ($user.Properties.Contains("userParameters"))
{
#Read the Property from ADSI
Write-Host $user.InvokeGet("TerminalServicesProfilePath")
} else {
#Set the property to construct the userParameter blob
$user.InvokeSet("TerminalServicesProfilePath", "\\somepath")
$user.CommitChanges()
}
Even if the userParameters attribute is not set, you can still perform an InvokeSet to have it constructed

Multiple parameter in powershell

I have a powershell script to call the SP which is shown below
$sqlTableConnection = New-Object [System.Data.SqlClient.SqlConnection]("Server=1xx.xx.xxx; Database=dbName; Integrated Security= True");
$sqlTableCmd = New-Object [System.Data.SqlClient.SqlCommand]
$sqlTableCmd.CommandText = "[System_Control].[usp_Check_system]"
$sqlTableCmd.Connection = $sqlTableConnection;
$jobName1 = "MASTER_JOB";
$sqlTableCmd.Parameters.AddWithValue("#JobName", $jobName1) ;
$outParameter = new-object System.Data.SqlClient.SqlParameter;
$outParameter.ParameterName = "#JobSatatus";
$outParameter.Direction = [System.Data.ParameterDirection]'Output';
$outParameter.DbType = [System.Data.DbType]'Boolean';
$sqlTableCmd.Parameters.Add($outParameter) ;
$sqlTableCmd.Connection = $sqlTableConnection
$sqlTableConnection.Open();
$sqlTableCmd.ExecuteNonQuery();
$truth = $sqlTableCmd.Parameters["#JobSatatus"].Value;
$sqlTableConnection.Close();
$truth;
but when I am running this I am getting below error
Exception calling "ExecuteNonQuery" with "0" argument(s): "The variable name '#
JobName' has already been declared. Variable names must be unique within a quer
y batch or stored procedure."
At line:15 char:33
+ $sqlTableCmd.ExecuteNonQuery <<<< ();
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Where I am doing mistake?
You should only be getting that error if you aren't re-initializing the SqlCommand object and so it has #JobName already defined.
I suspect you are getting this when you run it manually, not when you execute the script as a whole..
OR, if you are seeing it all the time, then maybe you moved this code into a function but the original $sqlTableCmd was initialized in the main code and now when this executes, it is in a different scope, but is getting confused by the one that was defined in the higher scope?
As I understand it, this line here is the one that should be setting things up fresh for you:
$sqlTableCmd = New-Object [System.Data.SqlClient.SqlCommand]

How to use InsertAfter with PowerShell

I have some xml files where I want to insert the contents of one xml file into another. I thought I'd use LastChild and the InsertAfter method to accomplish this. So far it's not working for me.
Here is the parent.xml file:
<manifest>
<manifestExecution>
<assetDetail>
<fileAsset fileAssetGuid="parentguid1">
<parentfile1 />
</fileAsset>
<fileAsset fileAssetGuid="parentguid2">
<parentfile2 />
</fileAsset>
</assetDetail>
</manifestExecution>
</manifest>
And here is the child.xml file:
<manifest>
<manifestExecution>
<assetDetail>
<fileAsset fileAssetGuid="childguid1">
<childfile1 />
</fileAsset>
</assetDetail>
</manifestExecution>
</manifest>
What I want to do is select the fileAsset node(s) from child.xml and insert into parent.xml after the last fileAsset node in parent.xml.
Here is my test code:
$parent = [xml] (Get-Content d:\temp\parent.xml)
$parentnode = $parent.manifest.manifestExecution.assetDetail
$child = [xml] (Get-Content d:\temp\child.xml)
$childnode = $child.manifest.manifestExecution.assetDetail.InnerXml
$parentnode.InsertAfter($childnode, $parentnode.LastChild)
Here is the error msg I'm getting:
Cannot convert argument "0", with value: "<fileAsset fileAssetGuid="childguid1"> <childfile1 /></fileAsset>", for "InsertAfter" to type "System.Xml.XmlNode": "Cannot conver
t the "<fileAsset fileAssetGuid="childguid1"><childfile1 /></fileAsset>" value of type "System.String" to type "System.Xml.XmlNode"."
At line:5 char:24
+ $parentnode.InsertAfter <<<< ($childnode, $parentnode.LastChild)
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
What am I doing wrong?
You need to iterate through $childnode's children, remove them from their parent, and import them into the new document context ($child and $parent are different XmlDocument instances) before appending to $parentnode.
This will append all fileAsset nodes from $childnode into $parentnode.
$parent = [xml](get-content d:\temp\parent.xml)
$parentnode = $parent.manifest.manifestexecution.assetdetail
$child = [xml](get-content d:\temp\child.xml)
$childnode = $child.manifest.manifestexecution.assetdetail
while ($childnode.haschildnodes) {
$cn = $childnode.firstchild
$cn = $childnode.removechild($cn)
$cn = $parentnode.ownerdocument.importnode($cn, $true)
$parentnode.appendchild($cn)
}
Fortunately, most of these methods return the same XmlNode or a new version of it, so the body of the while loop could chained together like this:
$parentnode.appendchild( $parentnode.ownerdocument.importnode( $childnode.removechild( $childnode.firstchild ), $true ))
InsertAfter(newChild,referenceChild) could also work, but would be done a little differently since it also needs a reference to the the node that it will be inserted after.
your first problem is that you're not getting an XML element, but a string. You need to get an XML node from your XML document, but the shorthand method you're using is guessing you want a string. Usually you can force it by explicitly casting it over to [System.Xml.XmlElement], but that doesn't always work. You can reliably get an element using "SelectSingleNode".
You've not hit your second problem yet, but it's just around the corner. Once you've got XML, it still won't work because it's from a different XML document, so you need to "Import" the node. You'll want to tweak this to get the XML to align the way you envision, but the code works.
$parentString = #"
<manifest>
<manifestExecution>
<assetDetail>
<fileAsset fileAssetGuid="parentguid1">
<parentfile1 />
</fileAsset>
<fileAsset fileAssetGuid="parentguid2">
<parentfile2 />
</fileAsset>
</assetDetail>
</manifestExecution>
</manifest>
"#
$childString = #"
<manifest>
<manifestExecution>
<assetDetail>
<fileAsset fileAssetGuid="childguid1">
<childfile1 />
</fileAsset>
</assetDetail>
</manifestExecution>
</manifest>
"#
$parent = [xml] ($parentString)
$parentnode = $parent.manifest.manifestExecution.assetDetail
$child = [xml] ($childString)
$xpath = '/manifest/manifestExecution/assetDetail'
$childnode = $child.SelectSingleNode($xpath)
Write-Host("So the child is $($childnode.OuterXML)")
$importedNode = $parent.ImportNode($childNode,$true)
Write-Host("And after importing: $($importedNode.OuterXML)")
$parentnode.InsertAfter($importednode, $parentnode.LastChild)
Write-Host("To finally yield: $($parent.OuterXML)")
Also, you may find you can use something like your original code if you cast it to XmlElement properly.
$childnode = [System.Xml.XmlElement]$child.manifest.manifestExecution.assetDetail.InnerXml