What does the `-Sanitize` switch of the `Clear-Disk` cmdlet do in PowerShell? - powershell

The Clear-Disk cmdlet seems to have a currently undocumented -Sanitize switch:
PS> Get-Command Clear-Disk | Select-Object -ExpandProperty ParameterSets | ? {$_.Name -like 'ByName'} | Select-Object -ExpandProperty Parameters | ? {$_.Name -like 'Sanitize'}
Name : Sanitize
ParameterType : System.Management.Automation.SwitchParameter
IsMandatory : False
IsDynamic : False
Position : -2147483648
ValueFromPipeline : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments : False
HelpMessage :
Aliases : {}
Attributes : {InputObject (cdxml), ByNumber, ByPath, ByName...}
What does it do? Does it trigger the ATA/SCSI command SANITIZE?

[Edit: this is incomplete; after all that it looks like Sanitize is its own separate, undocumented parameter]
There are a lot of cmdlets in PowerShell which were automatically generated wrappers around WMI. That mention of cdxml in your question is a giveaway that Clear-Disk is one of those.
Run Get-Command Clear-Disk | Format-List * and skim the results, it has:
OutputType: Microsoft.Management.Infrastructure.CimInstance#
ROOT/Microsoft/Windows/Storage/MSFT_Disk
So Clear-Disk gives the MSFT_Disk WMI class a nice PowerShell cmdlet wrapper. MSFT_Disk is documented here. This seems to be out of date.
Clear-Disk is part of the Storage module, and that is in
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Storage>
In there is Disk.cdxml which is the mapping from WMI to Cmdlet, which contains:
<DefaultNoun>Disk</DefaultNoun>
[...]
<CmdletMetadata Verb="Clear" ConfirmImpact="High" />
<Method MethodName="Clear">
NB. that PowerShell cmdlets are Verb-Noun, so this is the section for verb Clear on default noun disk to make Clear-Disk, and it maps to the method Clear() on the MSFT_Disk class, I think. Inside that section is:
<Parameters>
<!-- RemoveData -->
<Parameter ParameterName="RemoveData">
<Type PSType="System.Management.Automation.SwitchParameter" />
<CmdletParameterMetadata />
</Parameter>
<!-- RemoveOEM -->
<Parameter ParameterName="RemoveOEM">
<Type PSType="System.Management.Automation.SwitchParameter" />
<CmdletParameterMetadata />
</Parameter>
<!-- Sanitize -->
<Parameter ParameterName="Sanitize">
<Type PSType="System.Management.Automation.SwitchParameter" />
<CmdletParameterMetadata />
</Parameter>
<!-- SourceCaller -->
<Parameter ParameterName="cim:operationOption:SourceCaller" DefaultValue="Microsoft.PowerShell">
<Type PSType="System.String" />
</Parameter>
</Parameters>
</Method>
The MSFT_Disk documentation from earlier has a section documenting the Clear() Method.
That takes four parameters:
UInt32 Clear(
[in] Boolean RemoveData,
[in] Boolean RemoveOEM,
[in] Boolean ZeroOutEntireDisk,
[out] String ExtendedStatus
);
And the blob of XML seems to map those to PowerShell parameter names. I first thought these matched up and the ZeroOutEntireDisk was a rename of the third parameter, but that does not seem correct; Martin Smith has found a source which has both parameters mentioned.
ZeroOutEntireDisk [in] is documented as:
TRUE if this parameter instructs this method to zero out the entire disk in addition to removing all partition information. If this parameter is FALSE or NULL, only the first and last megabytes of the disk are zeroed.
C:\Windows\SysWOW64\wbem\en-GB\storagewmi.mfl describes Sanitize:
"If TRUE, this parameter instructs Clear to send command to the disk to physically obliterate data."
wbemtest is the WMI Tester, separate from PowerShell, and you can connect to the root\Microsoft\Windows\Storage namespace, Open Class the MSFT_Disk class, edit the Clear Method and get to the parameters there, and the MOF definition for the input parameters is:
[abstract]
class __PARAMETERS
{
[In, ID(0): DisableOverride ToInstance] boolean RemoveData;
[In, ID(1): DisableOverride ToInstance] boolean RemoveOEM;
[In, ID(2): DisableOverride ToInstance] boolean ZeroOutEntireDisk;
[In, ID(3): DisableOverride ToInstance] boolean Sanitize;
[In, ID(4): DisableOverride ToInstance] boolean RunAsJob;
};
I don't know how the CDXML definition matches these. How it can call a method using fewer parameters than the method takes, whether it can match them in order or by name, what cim:operationOption:SourceCaller does, and whether this method even works or not (anyone tried?)

Related

EventLogPropertySelector Not Returning Extended Data From Event Object In PowerShell

My short term goal is to gather event IDs 40 and 42 with provider Microsoft-Windows-TerminalServices-LocalSessionManager from the log named Microsoft-Windows-TerminalServices-**LocalSessionManager/Operational then sort them based on the Session/SessionID. While I could try and parse the message property, I'm trying to use the EventLogRecord's GetPropertyValues method which takes a System.Diagnostics.Eventing.Reader.EventLogPropertySelector parameter. Also, the session ID shows up first labeled "Session" for event 40's message and shows up second labeled "SessionID" for event 41's message.
Name
MemberType
Definition
GetPropertyValues
Method
System.Collections.Generic.IList[System.Object]
I've see that in 2018 Peter-Core and Mathias R. Jessen have both given answer on how to do this. Following their examples, I looked at the event provider's event template.
(Get-WinEvent -ListProvider Microsoft-Windows-TerminalServices-LocalSessionManager).Events|Where-object{#(40,42) -contains $_.ID}
Id : 40
Version : 0
LogLink : System.Diagnostics.Eventing.Reader.EventLogLink
Level : System.Diagnostics.Eventing.Reader.EventLevel
Opcode : System.Diagnostics.Eventing.Reader.EventOpcode
Task : System.Diagnostics.Eventing.Reader.EventTask
Keywords : {}
Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
<data name="Session" inType="win:UInt32" outType="xs:unsignedInt"/>
<data name="Reason" inType="win:UInt32" outType="xs:unsignedInt"/>
</template>
Description : Session %1 has been disconnected, reason code %2
Id : 42
Version : 0
LogLink : System.Diagnostics.Eventing.Reader.EventLogLink
Level : System.Diagnostics.Eventing.Reader.EventLevel
Opcode : System.Diagnostics.Eventing.Reader.EventOpcode
Task : System.Diagnostics.Eventing.Reader.EventTask
Keywords : {}
Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
<data name="User" inType="win:UnicodeString" outType="xs:string"/>
<data name="SessionID" inType="win:UInt32" outType="xs:unsignedInt"/>
</template>
Description : End session arbitration:
User: %1
Session ID: %2
As a test, I created the following to try and get the Session ID from the event ID 42:
$strComputerName='PMV-SC01'
<# For those wanting to build xmlFilters try something like the following as it allow you to create them more dynamically
$xmlFilterBase=[xml]'<QueryList><Query Id="0"></Query></QueryList>'
## in a loop ##
$xmlSelect=$xmlFilter.CreateElement("Select")
$xmlSelect.SetAttribute("Path","$Logname")
$xmlSelect.set_InnerText("*[System[$($ProviderName)TimeCreated[#SystemTime>='$StartTime' and #SystemTime<='$EndTime']]]")
$xmlFilter.QueryList.Query.AppendChild($xmlSelect)|Out-Null
$xmlSelect=$Null
## end loop ##
#>
#I used the filter below to simplify the testing.
$xmlFilter=#"
<QueryList><Query Id="0" Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational"><Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[TimeCreated[#SystemTime>='2021-06-28T22:00:00.000Z' and #SystemTime<='2021-06-28T23:00:00.000Z']]]</Select></Query></QueryList>
"#
$WinEvents=Get-WinEvent -ComputerName $strComputerName -FilterXml $xmlFilter -Oldest|Where-object{$_.Id -eq 42}
$WinEvents.count
# output is 10
$SelectorStrings= [String[]]#(
'Event/EventData/Data[#name="SessionID"]'
)
$PropertySelector= [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
$WinEvents|ForEach-Object{$TheSessionIds=#();$TheSessionIds+=$_.GetPropertyValues($PropertySelector)}
$TheSessionIds.count
#output is 1
# All of the follow also return nothing:
$WinEvents[1].GetPropertyValues($PropertySelector)
$WinEvents.GetPropertyValues($PropertySelector)
$1WinEvent=$WinEvents[0]
$1WinEvent.GetPropertyValues($PropertySelector)
There is no error message. The GetPropertyValues() method just returns nothing.
PowerShell Version: 5.1.14393.2457
Windows
Edition
Version
OS Build
Windows
Server 2016 Standard
1607
14393.2457
(Get-WindowsVersion credit)
My long term goal is generating something like following output. The request on this post is help getting values back from the GetPropertyValues method. I'd also like to use this method in other projects.
SessionID
User
TimeCreated
ID
Code
12
MYDOMAIN\User.Name
2021-06-28 07:35:54.4321
42
12
2021-06-28 15:35:54.4321
40
0
13
MYDOMAIN\Other.User
2021-06-28 11:12:44.1234
42
13
2021-06-28 13:01:12.5678
40
0
If you examine a sample XML record you can see the path to the node is not under /EventData/Data. It took some fiddling but this worked perfect for me.
$xmlFilter = #"
<QueryList>
<Query Id="0" Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">
<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[(EventID=40 or EventID=42)]]</Select>
</Query>
</QueryList>
"#
$SelectorStrings= [String[]]#(
'Event/UserData/EventXML/User',
'Event/UserData/EventXML/Session',
'Event/UserData/EventXML/Reason'
)
Get-WinEvent -FilterXml $xmlFilter | ForEach-Object {
switch ($_.id){
40 {$SelectorStrings[1] = 'Event/UserData/EventXML/Session'}
42 {$SelectorStrings[1] = 'Event/UserData/EventXML/SessionID'}
}
$PropertySelector= [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
$user,$sessionID,$reason = $_.GetPropertyValues($PropertySelector)
[PSCustomObject]#{
SessionID = $sessionID
User = $user
TimeCreated = $_.TimeCreated
ID = $_.ID
Code = $reason
}
}

How to read XML-Data into a dataTable via Powershell and force using correc types via a given XmlSchema?

I am currently struggling to read XMLdata into a dataTable by enforcing a given XmlSchema.
Whatever I do, after the Data-Import all Types are set back to "string".
I need to force the below ID-column to be of type "int" (not "string" or "byte"):
$schema = '
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:group name="r">
<xs:sequence>
<xs:element name="id" type="xs:int" />
<xs:element name="name" type="xs:string" />
</xs:sequence>
</xs:group>
<xs:complexType name="body">
<xs:group ref="r" />
</xs:complexType>
</xs:schema>'
$data = '
<body>
<r>
<id>9</id>
<name>AAA</name>
</r>
<r>
<id>10</id>
<name>BBB</name>
</r>
</body>'
# read the schema:
$set = [System.Data.Dataset]::new()
$sr =[System.IO.StringReader]::new($schema)
$set.ReadXmlSchema($sr)
# read the data:
$sr =[System.IO.StringReader]::new($data)
$set.ReadXml($sr)
cls
$set.Tables | ft -AutoSize
write-host "type of column 'id' in table : " $set.Tables[0].Columns[0].DataType
$list = [System.Collections.ArrayList]::new($set.Tables[0].GetList())
write-host "type of column 'id' in list : " $list[0].id.GetType()
I also did some tests with the XMLReadMode, but the only change appears, when I use [System.Data.XmlReadMode]::InferTypedSchema, but this changes the type to "byte" or something else depending on the data for that column.
Any help is more than welcome here!
Thanks you in advance.
Thanks to the hints I finally solved it.
First I found out, that I dont need to create a dedicated XMLSchema before the data-import - just creating the proper table-structure did the same thing with less code.
Here the final code-sample:
cls
$set =[System.Data.DataSet]::new()
$set.tables.Add('r')
$table = $set.Tables[0]
[void]$table.Columns.Add('id', [int])
[void]$table.Columns.Add('name', [string])
$data = '
<body>
<r>
<id>9</id>
<name>AAA</name>
</r>
<r>
<id>10</id>
<name>BBB</name>
</r>
</body>'
# read the data:
$sr =[System.IO.StringReader]::new($data)
[void]$set.ReadXml($sr)
$set.Tables | ft -AutoSize
write-host "type of column 'id' in table : " $table.Columns['id'].DataType
$list = [System.Collections.ArrayList]::new($table.GetList())
write-host "type of column 'id' in list : " $list[0].id.GetType()
Here the output from above script:
id name
-- ----
9 AAA
10 BBB
type of column 'id' in table : System.Int32
type of column 'id' in list : System.Int32

pass json as parameter in power shell script

I have made a function for creating new xml node.there are two parameters in my function on is a existing xml file reference and second one is element value.while running the script its showing an error
code
function createProviderNode($xmlData,$propertyValue){
Write-Host 'inside createProviderNode'
Write-Host ($propertyValue)
#[xml]$xmlData = get-content E:\powershell\data.xml
$newProviderNode = $xmlData.CreateNode("element","provider","")
$newProviderNode.SetAttribute("name",$propertyValue)
$xmlData.SelectSingleNode('providers').AppendChild($newProviderNode)
$xmlData.save("E:\powershell\data.xml")
}
did i miss anything in this code?
The error message implies that while you expected $xmlData to contain an object of type [xml] (System.Xml.XmlDocument) - i.e., an XML document - in reality it was a string ([string]).
In other words: When you called your createProviderNode function, the 1st argument you passed was a string, not an XML document (of type [xml]).
Typing your $xmlData parameter variable as [xml] solves this problem, as that will implicitly covert even a string argument to an XML document on demand - if possible.
A simplified example, using a script block in lieu of a function:
$xmlString = #'
<?xml version="1.0"?><catalog><book id="bk101"><title>De Profundis</title></book></catalog>
'#
# Note how $xmlData is [xml]-typed.
& { param([xml] $xmlData) $xmlData.catalog.book.title } $xmlString
The above yields De Profundis, demonstrating that the string argument was converted to an [xml] instance (which - thanks to PowerShell's type adaptation magic - makes the element names available as direct properties).
It is then safe to call the .CreateNode() method on $xmlData.
Well, you don't show your original XML format.
Why did you comment out that Get-Content? it will not work without it.
So, if we take the below example, it works as expected.
# Simple XML version
$SimpleXml = $null
$SimpleXml = #"
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<name>Apple</name>
<size>1234</size>
</configuration>
"#
# New node code
[xml]$XmlDoc = Get-Content -Path variable:\SimpleXml
$runtime = $XmlDoc.CreateNode("element","runtime","")
$generated = $XmlDoc.CreateNode("element","generatePublisherEvidence","")
$generated.SetAttribute("enabled","false")
$runtime.AppendChild($generated)
$XmlDoc.configuration.AppendChild($runtime)
$XmlDoc.save("$pwd\SimpleXml.xml")
Get-Content -Path "$pwd\SimpleXml.xml"
# Which creates this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<name>Apple</name>
<size>1234</size>
<runtime>
<generatePublisherEvidence enabled="false" />
</runtime>
</configuration>
Also Write-Host is never needed unless you are coloring screen output.
Write-Output is the default and automatically write to the screen, whether you specify Write-Output or not.
So, all of these to the same thing - output to the screen.
$SomeString = 'hello'
Write-Host $SomeString
Write-Output $SomeString
'hello'
"hello"
$SomeString
"$SomeString"
($SomeString)
("$SomeString")
$($SomeString)
# Results
hello
hello
hello
hello
hello
hello
hello
… yet, it's your choice.

How to add a numeric switch to a PowerShell script

I know how to add switch parameters to PowerShell scripts, but the name of the switch is always a (valid) identifier like -Enable because the name is generated from the backing PowerShell variable.
[CmdletBinding()]
param(
[switch] $Enable = $false
)
Some tools have switches like -2008. Normally, one would name the switch $2008 but this is not a valid identifier.
How can I implement such a switch as a boolean value in PowerShell?
Or in other words: How to specify a different parameter name then the backing variable?
Edit 1
I wasn't aware of number only variables (which is very strange for a programming language...). Anyhow, I created that example:
function foo
{ [CmdletBinding()]
param(
[switch] $2008
)
Write-Host "2008=$2008"
}
For this code, which is accepted as valid PowerShell, I get a auto completion as wanted. But when providing that parameter, I get this error message:
foo : Es wurde kein Positionsparameter gefunden, der das Argument "-2008" akzeptiert.
In Zeile:1 Zeichen:1
+ foo -2008
+ ~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [foo], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,foo
Translation: No positional parameter was found, that accepts the argument "-2008".
The purpose of the script is to provide translations / wrappers for command line interfaces.
Here is a set of standardized executables with parameters:
vcom.exe -2008 design.vhdl
vsim.exe -2008 design
Translation:
ghdl.exe -a --std=2008 design.vhdl
ghdl.exe -e --std=2008 design
ghdl.exe -r --std=2008 design
I would like to keep the feature of auto completion for parameters, otherwise I could process all remaining parameters and translate them by hand.
PowerShell doesn't support numeric parameters (See this answer).
Could a validateset be an acceptable solution to your problem ?
Validate set does benefit from the autocompletion feature and this is the next best thing, in my opinion, of what you wanted.
Foo & Foos — both are the same, except Foos accept multiple parameters.
foo -std 2008
foos -std 2008,2009,2010
function foo
{ [CmdletBinding()]
param(
[ValidateSet("2008",
"2009",
"2010",
"2011","2012")][INT]$std
)
Write-Host "std:$std"
}
function foos
{ [CmdletBinding()]
param(
[ValidateSet("2008",
"2009",
"2010",
"2011","2012")][INT[]]$std
)
$std | foreach {Write-Host "std: $_"}
}
foo -std 2008
foos -std 2008,2009,2010

Powershell function Params getting unexpected values when CSV fields are not present

I am writing a PowerShell function which can take pipeline input. Specifically I am testing it with Import-CSV. Many of the params are not mandatory, which means sometimes the CSV will not have those columns. For boolean values this is working as expected, but with string values, a missing CSV field yields a copy of the row object in the string field.
Here is an example problem parameter:
[Parameter(Mandatory=$False,
ValueFromPipeline=$True,
ValueFromPipelinebyPropertyName=$True,
HelpMessage="TCP Port of the remote server")]
[Alias('Port', 'Remote Server Port')]
[string]$RemoteServerPort = "5500",
Now, if the field is missing, I would expect the value to be "5500" as specified, but instead I get:
$RemoteServerPort = #{Name=KG; IP=10.1.1.1; Username=Admin; Password=}
I've done some looking around, but frankly I'm not even sure what to search for.
This is because you specified ValueFromPipeline=$True so that PoSh coerces the piped object to a string if it cannot bind the parameter by property name. You could solve that by removing ValueFromPipeline=$True from this parameter and introduce another one to be bound to the piped object, i.e. something like this
function MyTestFunc() {
param(
[Parameter(ValueFromPipeline=$True)]
[object]$PipedObj,
[Parameter(Mandatory=$False,
ValueFromPipelinebyPropertyName=$True,
HelpMessage="TCP Port of the remote server")]
[Alias('Port', 'Remote Server Port')]
[string]$RemoteServerPort = "5500"
)
Write-Host "Port: $RemoteServerPort / Obj: $PipedObj"
}
$o1 = [pscustomobject]#{"Server" = "123"; "Port" = "12345"}
$o2 = [pscustomobject]#{"Server" = "1234"; "OS" = "Win3.1"}
$o1 | MyTestFunc
$o2 | MyTestFunc
Will result in
Port: 12345 / Obj: #{Server=123; Port=12345}
Port: 5500 / Obj: #{Server=1234; OS=Win3.1}
A way to see in detail what is actually happening behind the scenes is to use Trace-Command like so
Trace-Command ParameterBinding {$o2 | MyTestFunc} -PSHost