I have the following code:
$xml = 'C:\Users\jonb\Desktop\bom.xml'
$ns = #{a = 'http://cyclonedx.org/schema/bom/1.2'}
$xPath = '//a:component'
$components = Select-Xml -Xml $xml -XPath $xPath -Namespace $ns
foreach ($component in $components) {
$name = Select-Xml -Xml $component.Node -XPath './/a:name/text()'-Namespace $ns
$version = Select-Xml -Xml $component.Node -XPath './/a:version/text()'-Namespace $ns
$lic_cond = Select-Xml -Xml $component.Node -XPath './/a:license/a:id/text()'-Namespace $ns
$license = $(If ($lic_cond) {$lic_cond} Else {"NA"})
$finalObject = [pscustomobject]#{
'Name' = $name
'Version' = $version
'License' = $license
}
Write-Output $finalObject
}
Now I would like to convert my $finalObject to a MarkDown Table. Are there any possibilities here?
I have seen that there is a ConvertFrom-Markdown command. However, I did not get anywhere with this command.
Thanks for your help
Just a quick and dirty one:
function ConvertTo-MarkDownTable {
[CmdletBinding()] param(
[Parameter(Position = 0, ValueFromPipeLine = $True)] $InputObject
)
Begin { $Index = 0 }
Process {
if ( !$Index++ ) {
'|' + ($_.PSObject.Properties.Name -Join '|') + '|'
'|' + ($_.PSObject.Properties.ForEach({ '-' }) -Join '|') + '|'
}
'|' + ($_.PSObject.Properties.Value -Join '|') + '|'
}
}
usage:
$finalObject =
[pscustomobject]#{ 'Name' = 'name1'; 'Version' = 'version1'; 'License' = 'license1' },
[pscustomobject]#{ 'Name' = 'name2'; 'Version' = 'version2'; 'License' = 'license2' }
$finalObject |ConvertTo-MarkDownTable
Yields:
|Name|Version|License|
|-|-|-|
|name1|version1|license1|
|name2|version2|license2|
Converted in StackOverflow:
Name
Version
License
name1
version1
license1
name2
version2
license2
Related
I have the following code:
$xmlFile = 'C:\Users\kraer\Desktop\bom.xml'
[xml]$xml = Get-Content $xmlFile
$xml.bom.components.component | ForEach-Object {
$finalObject = [PSCustomObject]#{
'Name' = $_.name
'Version' = $_.version
'License' = $_.licenses.license.id
}
Write-Output $finalObject
}
Now I would like to convert my $finalObject to a MarkDown Table. Are there any possibilities here?
For another question I received this answer but right now it doesn't work for my code.
function ConvertTo-MarkDownTable {
[CmdletBinding()] param(
[Parameter(Position = 0, ValueFromPipeLine = $True)] $InputObject
)
Begin { $Init = $True }
Process {
if ( $Init ) {
$Init = $False
$_.PSObject.Properties.Name -Join '|'
$_.PSObject.Properties.ForEach({ '-' }) -Join '|'
}
$_.PSObject.Properties.Value -Join '|'
}
}
Do you have another solution?
Thanks for your help
Not knowing the contents of your bom.xml, you might try this slightly adapted version of the function:
function ConvertTo-MarkDownTable {
[CmdletBinding()] param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
$InputObject
)
Begin {
$headersDone = $false
$pattern = '(?<!\\)\|' # escape every '|' unless already escaped
}
Process {
if (!$headersDone) {
$headersDone = $true
# output the header line and below that a dashed line
# -replace '(?<!\\)\|', '\|' escapes every '|' unless already escaped
'|{0}|' -f (($_.PSObject.Properties.Name -replace $pattern, '\|') -join '|')
'|{0}|' -f (($_.PSObject.Properties.Name -replace '.', '-') -join '|')
}
'|{0}|' -f (($_.PsObject.Properties.Value -replace $pattern, '\|') -join '|')
}
}
Usage:
# load the xml from file
$xml= New-Object System.XML.XMLDocument
$xml.Load('C:\Users\kraer\Desktop\bom.xml')
$finalObject = $xml.bom.components.component | ForEach-Object {
[PSCustomObject]#{
'Name' = $_.name
'Version' = $_.version
'License' = $_.licenses.license.id
}
}
# convert to markdown
$finalObject | ConvertTo-MarkDownTable
P.S. $_.licenses.license.id might be wrong, because it looks like licenses is an array of licences. You would probably want to do something like this here:
($_.licenses | ForEach-Object { $_.license.id }) -join '; '
I have INI file and I want to get specific section. The items in the section that I choose are 24 items. I want to use all the item to write in a file. I tried this, It works, but it looks like bad way to write 24 times to do the process. Is there any other way to do that more beautiful? The section of my INI file like this
Input ini:
[Code]
A1=12,34,56
A2=23,45,67
A3=34,56,78,9,10
...
A24=a1,b2,c3,d4,e5
Script:
Function F_ML
{
$FilePath = "C:\Users\File.ini"
$section = "Code"
$R_1 = "A1"
$R_2 = "A2"
$R_3 = "A3"
$R_4 = "A4"
$R_5 = "A5"
$R_6 = "A6"
$R_7 = "A7"
$R_8 = "A8"
$R_9 = "A9"
$R_10 = "A10"
$R_11 = "A11"
$R_12 = "A12"
$R_13 = "A13"
$R_14 = "A14"
$R_15 = "A15"
$R_16 = "A16"
$R_17 = "A17"
$R_18 = "A18"
$R_19 = "A19"
$R_20 = "A20"
$R_21 = "A21"
$R_22 = "A22"
$R_23 = "A23"
$R_24 = "A24"
$store = "C:\Users\"
$FilePath
$input_file = $FilePath
$ini_file = #{}
Get-Content $input_file | ForEach-Object {
$_.Trim()
} | Where-Object {
$_ -notmatch '^(;|$)'
} | ForEach-Object {
if ($_ -match '^\[.*\]$') {
$section = $_ -replace '\[|\]'
$ini_file[$section] = #{}
} else {
$key, $value = $_ -split '\s*=\s*', 2
$ini_file[$section][$key] = $value
}
}
#--------
$Path_Store = $store
#---------
$Get_1 = $ini_file.($section).($R_1)
$L_1 = $Get_1.Substring(0,3)
$Get_2 = $ini_file.($section).($R_2)
$L_2 = $Get_2.Substring(0,3)
$Get_3 = $ini_file.($section).($R_3)
$L_3 = $Get_3.Substring(0,3)
#---------
$Outer = ";********************"
$Header = ";*******************"
$ML = "12345"
$FB = ";Initial=1a2b"
#----------
$B_ID_1 = ";Build=" + $ML + "#" + "S" + $L_1 + "#" + "D" + $L_1
$CRM_1 = ";CRM=" + $R_1
$Output_1 = $Header, $B_ID_1, $FB, $CRM_1 , $Outer | Out-File $Path_Store\A1
$B_ID_2 = ";Build=" + $ML + "#" + "S" + $L_2 + "#" + "D" + $L_2
$CRM_2 = ";CRM=" + $R_2
$Output_2 = $Header, $B_ID_2, $FB, $CRM_2 , $Outer | Out-File $Path_Store\A2
$B_ID_3 = ";Build=" + $ML + "#" + "S" + $L_3 + "#" + "D" + $L_3
$CRM_3 = ";CRM=" + $R_3
$Output_3 = $Header, $B_ID_3, $FB, $CRM_3 , $Outer | Out-File $Path_Store\A3
#---------
}
$call = F_ML
My expectation, I can make this way shorter and the output is getting 24 output file.
Output Sample
Output File 1
;********************
;Build=12345#S12#D12
;Initial=1a2b
;CRM=A1
;********************
Output File 2
;********************
;Build=12345#S23#D23
;Initial=1a2b
;CRM=A2
;********************
Try below...
$IniContent = Get-Content -Path $IninPath
$IniContent | ForEach-Object -Process {
$Split = $_ -split '=';
$OutPutfilePath = "c:\temp\$($Split[0]).txt"
$first = ($Split[1] -split ',')[0]
$append = "#S{0}D{1}" -f $first,$first
# create a herestring to build the output
#"
;********************
;Build=$Ml$append
$FB
;CRM=$($Split[0])
;********************
"# | Out-File -Path $OutPutfilePath -Force
}
Use a ForEach to manipulate a string and Invoke the Expression.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$R_{0} = "A{0}"' -f $_
)}
.
.
.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$Get_{0} = $ini_file.($section).($R_{0}) `
$L_{0} = $Get_{0}.Substring(0,3)' -f $_
)}
.
.
.
1..24 | ForEach-Object { Invoke-Expression -Command (
'$B_ID_{0} = ";Build=" + $ML + "#" + "S" + $L_{0} + "#" + "D" + $L_{0}
$CRM_{0} = ";CRM=" + $R_{0}
$Output_{0} = $Header, $B_ID_{0}, $FB, $CRM_{0} , $Outer | Out-File $Path_Store\A{0}' -f $_
)}
{0} will be replaced by the value behind -f so it will be replaced Foreach number from 1 to 24...
Would this work?
All I did here was use your existing code to run everything in a loop from 1 to 24, removing the duplicated code. I reformatted it a little so it was a little easier for me to read.
Essentially, the variable $i will be a number from 1 to 24, while the variable $R will be "A" and whatever number is in $i (essentially A + $i)
Function F_ML
{
$section = "Code"
$store = "C:\Users\"
$input_file = "C:\Users\File.ini"
$ini_file = #{}
for ($i=1; $i -le 24; $i++)
{
$R = "A$($i)"
Get-Content $input_file |
ForEach-Object `
{
$_.Trim()
} |
Where-Object `
{
$_ -notmatch '^(;|$)'
} |
ForEach-Object `
{
if ($_ -match '^\[.*\]$')
{
$section = $_ -replace '\[|\]'
$ini_file[$section] = #{}
}
else
{
$key, $value = $_ -split '\s*=\s*', 2
$ini_file[$section][$key] = $value
}
}
#--------
$Path_Store = $store
#---------
$Get = $ini_file.($section).($R)
$L = $Get.Substring(0,3)
#---------
$Outer = ";********************"
$Header = ";*******************"
$ML = "12345"
$FB = ";Initial=1a2b"
#----------
$B_ID = ";Build=" + $ML + "#" + "S" + $L + "#" + "D" + $L
$CRM = ";CRM=" + $R
$Output = $Header, $B_ID, $FB, $CRM , $Outer | Out-File $Path_Store\$R
}
}
I created a report, if I run it manually from Powershell ISE, it generates the list of items I am expecting, but when I run it from Reporting Tools it returns no results.
The script scrapes all the items versions and languages, which are around 80,000 items and this takes a while.
Is there a way to add a delay until the list of all items is generated, or any other workaround ?
Source code:
$RichTextContentID = "";
$internalLinkPattern = '<a href="~\/link\.aspx\?_id=(?<sitecoreid>[a-zA-Z\d]{32})&_z=z">';
$literatureTemplate = "";
$global:guiltyItems = #();
function Process-RichText
{
param( [Parameter(Mandatory = $true)] [Sitecore.Data.Fields.Field]$field,
[Parameter(Mandatory = $true)] [string]$pattern,
[Parameter(Mandatory = $true)] [Sitecore.Data.Items.Item]$item)
$allMatches = [System.Text.RegularExpressions.Regex]::Matches($field.Value,$pattern);
foreach ($match in $allMatches)
{
$currentItem = Get-Item master -Id ([Sitecore.Data.ID]::Parse($match.Groups["sitecoreid"].Value)).Guid;
if ($currentItem.Template.Id -eq $literatureTemplate)
{
if ($global:guiltyItems -notcontains $item)
{
$global:guiltyItems += $item;
}
}
}
}
$allitems = Get-Item master -Query "/sitecore/content/MyWebsiteTree//*" -Language * -Version *;
foreach ($item in $allItems) {
foreach ($field in $item.Fields)
{
if ($field.Id -eq $RichTextContentID -and ($field.Value -match $internalLinkPattern))
{
Process-RichText $field $internalLinkPattern $item;
}
}
}
if ($global:guiltyItems.Count -eq 0) {
Show-Alert "Did not find any items to match your condition.";
}
else {
$props = #{
Title = ""
InfoDescription = ""
PageSize = 50
};
($global:guiltyItems) |
Show-ListView #props -Property #{ Label="Item name"; Expression={$_.Name}; },
#{ Label="ID"; Expression={$_.ID}; },
#{ Label="Display name"; Expression={$_.DisplayName}; },
#{ Label="Language"; Expression={$_.Language}; },
#{ Label="Version"; Expression={$_.Version}; },
#{ Label="Path"; Expression={$_.ItemPath}; },
#{ Label="Created"; Expression={$_.__Created}; },
#{ Label="Created by"; Expression={$_."__Created by"}; },
#{ Label="Updated"; Expression={$_.__Updated}; },
#{ Label="Updated by"; Expression={$_."__Updated by"}; }
}
Close-Window;
Thanks
LE: The object $allitems takes a while to be populated and the sitecore client does not wait for the backend to read all the items, and thus when I generate the report, $global:guiltyItems is always empty.
I have found the solution: using filters. And it works as it should.
$RichTextContentID = "";
$internalLinkPattern = '<a href="~\/link\.aspx\?_id=(?<sitecoreid>[a-zA-Z\d]{32})&_z=z">';
$literatureTemplateID = "";
$root = Get-Item -Path "master:/sitecore/content/MyWebsite";
filter Where-HasLiterature{
param([Parameter(Mandatory=$TRUE,ValueFromPipeline=$TRUE)][Sitecore.Data.Items.Item]$item)
if($item)
{
foreach ($field in $item.Fields)
{
if ($field.Id -eq $RichTextContentID -and ($field.Value -match $internalLinkPattern))
{
$allMatches = [System.Text.RegularExpressions.Regex]::Matches($field.Value,$internalLinkPattern);
foreach ($match in $allMatches)
{
$guiltyItem = Get-Item "master:" -Id ([Sitecore.Data.ID]::Parse($match.Groups["sitecoreid"].Value)).Guid;
$guiltyItemTemplate = [Sitecore.Data.Managers.TemplateManager]::GetTemplate($guiltyItem);
if ($guiltyItem -ne $null -and $guiltyItemTemplate.DescendsFromOrEquals($literatureTemplateID) )
{
$item;
}
}
}
}
}
}
$items = Get-ChildItem -Path $root.ProviderPath -Recurse | Where-HasLiterature
if ($items.Count -eq 0)
{
Show-Alert "Did not find any items to match your condition.";
}
else
{
$props = #{
Title = ""
InfoDescription = ""
PageSize = 50
}
$items | Show-ListView #props -Property #{ Label="Item name"; Expression={$_.Name}; },
#{ Label="ID"; Expression={$_.ID}; },
#{ Label="Display name"; Expression={$_.DisplayName}; },
#{ Label="Language"; Expression={$_.Language}; },
#{ Label="Version"; Expression={$_.Version}; },
#{ Label="Path"; Expression={$_.ItemPath}; },
#{ Label="Created"; Expression={$_.__Created}; },
#{ Label="Created by"; Expression={$_."__Created by"}; },
#{ Label="Updated"; Expression={$_.__Updated}; },
#{ Label="Updated by"; Expression={$_."__Updated by"}; }
}
I've been trying to compare two hashes of hashes against each other. Unfortunately even with the great help from here I struggled to pull together what I was trying to do.
So, I've resorted to searching the internet again and I was lucky enough to find Edxi's code (full credit to him and Dbroeglin who looks to have written it originally)https://gist.github.com/edxi/87cb8a550b43ec90e4a89d2e69324806 His compare-hash function does exactly what I needed in terms of its comparisons. However, I'd like to report of the full path of the hash in the final output. So I've tried to update the code to allow for this. My thinking being that I should be able to take the Key (aka path) while the code loops over itself but I'm clearly going about it in the wrong manner.
Am I going about this with the right approach, and if not, would someone suggest another method please? The biggest problem I've found so far is that if I change the hash, my code changes don't work e.g. adding 'More' = 'stuff' So it becomes $sailings.Arrivals.PDH083.More breaks the path in the way I've set it.
My version of the code:
$Sailings = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '10:11'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'OnTime'
'Other' = #{'Data' = 'Test'}
}
}
}
$Flights = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port'
'Scheduled' = '09:05'
'Expected' = '10:20'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port11'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'NotOnTime'
'Other' = #{'Data' = 'Test_Diff'}
}
}
}
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string] $path,
[boolean] $trackpath = $True
)
write-host "PAth received as: $path"
function New-Result($Key, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Key
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
[Object[]]$Results = $Left.Keys | ForEach-Object {
if ($trackpath ) {
write-host "Working on Path: " $path + $_
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $path"
New-Result $path $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
if ($path -ne $null -or $path -ne "/") {
$path = $path + "/" + $_
write-host "Sending Path to function as: $path"
}
Compare-Hashtable $Left[$_] $Right[$_] $path
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
$path = $path + "/" + $_
write-host "Not a hash so must be a value at path:$path"
New-Result $path $LValue "!=" $RValue
}
else {
Write-Host "Before changing path: $path "
if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
$path = $path.Substring(0, $path.lastIndexOf('/'))
Write-Host "After changing path: $path "
}
}
}
}
# if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
# Tried to use this to stop error on substring being less than zero
# but clearly doesnt work when you add more levels to the hash
$path = $path.Substring(0, $path.lastIndexOf('/'))
# } else { $path = $path.Substring(0, $path.lastIndexOf('/')) }
}
$Results += $Right.Keys | ForEach-Object {
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $_ $Null "=>" $Right[$_]
}
}
if ($Results -ne $null) { $Results }
}
cls
Compare-Hashtable $Sailings $Flights
outputs
key lvalue side rvalue
--- ------ ---- ------
/Arrivals/PDH083/Expected 10:11 != 10:20
/Arrivals/GoingTo Port1 != Port
/Departures/PDH083/ArrivingFrom Port1 != Port11
/Departures/PDH083/Status OnTime != NotOnTime
/Departures/PDH083/Other/Data Test != Test_Diff
I won't do that much string manipulation on the $Path but threat $Path as an array and join it to a string at the moment you assign it as a property (key = $Path -Join "/") to the object:
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string[]] $path = #(),
[boolean] $trackpath = $True
)
write-host "Path received as: $path"
function New-Result($Path, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Path -Join "/"
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
$Left.Keys | ForEach-Object {
$NewPath = $Path + $_
if ($trackpath ) {
write-host "Working on Path: " $NewPath
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $NewPath"
New-Result $NewPath $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
Compare-Hashtable $Left[$_] $Right[$_] $NewPath
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
New-Result $NewPath $LValue "!=" $RValue
}
}
}
}
$Right.Keys | ForEach-Object {
$NewPath = $Path + $_
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $NewPath $Null "=>" $Right[$_]
}
}
}
cls
Compare-Hashtable $Sailings $Flights
side-note: Write Single Records to the Pipeline (SC03), see: Strongly Encouraged Development Guidelines. In other words, don't do the $Results += ... but intermediately put any completed result in the pipeline for the next cmdlet to be picked up (besides, it unnecessarily code)...
I've created two functions in PowerShell, one that creates the Namespace ROOT\CustomCMClasses and one that creates the class Test. This piece works fine:
Param (
$Namespace = 'CustomCMClasses',
$Class = 'Test'
)
Function New-WMINamespaceHC{
if (Get-WmiObject -Namespace 'root' -Class '__NAMESPACE' | Where-Object {$_.Name -eq $Namespace}) {
Write-Verbose "WMI Namespace 'root\$Namespace' exists"
}
else {
Write-Verbose "Create WMI namespace 'root\$Namespace'"
$Ns = [WMICLASS]'root:__Namespace'
$NewNamespace = $Ns.createInstance()
$NewNamespace.Name = $Namespace
$NewNamespace.Put()
}
}
Function New-WMIClassHC {
if (Get-WmiObject -List -Namespace "root\$Namespace" | Where-Object {$_.Name -eq $Class}) {
Write-Verbose "WMI Class '$Class' exists"
}
else {
Write-Verbose "Create WMI Class '$Class'"
$NewClass = New-Object System.Management.ManagementClass ("root\$Namespace", [String]::Empty, $Null);
$NewClass['__CLASS'] = $Class
$NewClass.Qualifiers.Add('Static', $true)
$NewClass.Properties.Add('Key', [System.Management.CimType]::String, $false)
$NewClass.Properties['Key'].Qualifiers.Add('Key', $true)
$NewClass.Properties.Add('Value1', [System.Management.CimType]::UInt32, $false)
$NewClass.Properties.Add('Value2', [System.Management.CimType]::String, $false)
$NewClass.Put()
}
}
At this point I need to grant Domain users permissions on the new namespace, so they are able to write data to it on their local client (no remoting needed). This is the point where I find a lot of information but am now stuck.
On the Microsoft blog there is a script I tried to tweak, but it's overly complicated for my needs and it failed so I found and tweaked the following code:
Function Add-WMIPermissions {
[CmdLetBinding()]
Param (
[String]$Principal = 'DOMAIN.NET\Domain users',
[String]$Namespace = 'CustomCMClasses'
)
Function Get-Sid {
Param (
$DSIdentity
)
$ID = new-object System.Security.Principal.NTAccount($DSIdentity)
Return $ID.Translate([System.Security.Principal.SecurityIdentifier]).toString()
}
$Sid = Get-Sid $Principal
$WMISDDL = "A;CI;CCWP;;;$Sid"
$WMISDDLPartialMatch = "A;\w*;\w+;;;$Sid"
$security = Get-WmiObject -Namespace root\$Namespace -Class __SystemSecurity
$binarySD = #($null)
$result = $security.PsBase.InvokeMethod('GetSD',$binarySD)
$converter = New-Object system.management.ManagementClass Win32_SecurityDescriptorHelper
$CurrentWMISDDL = $converter.BinarySDToSDDL($binarySD[0])
if (($CurrentWMISDDL.SDDL -match $WMISDDLPartialMatch) -and ($CurrentWMISDDL.SDDL -notmatch $WMISDDL)) {
$NewWMISDDL = $CurrentWMISDDL.SDDL -replace $WMISDDLPartialMatch, $WMISDDL
}
else {
$NewWMISDDL = $CurrentWMISDDL.SDDL += '(' + $WMISDDL + ')'
}
$WMIbinarySD = $converter.SDDLToBinarySD($NewWMISDDL)
$WMIconvertedPermissions = ,$WMIbinarySD.BinarySD
if ($CurrentWMISDDL.SDDL -match $WMISDDL) {
Write-Verbose 'Current WMI Permissions matches desired value'
}
else {
$result = $security.PsBase.InvokeMethod('SetSD',$WMIconvertedPermissions)
if($result='0'){
Write-Verbose 'WMI permissions applied'
}
}
}
Add-WMIPermissions -Verbose
It's clearly stating that the permissions are correctly applied but the user still can't write data to WMI. Working with WMI is new to me, so any help is greatly appreciated.
The test code to see if a regular user (Domain users) has permissions:
$WMIClass = [WMICLASS]('root\' + $Namespace + ':' + $Class)
$WMIInstance = $WMIClass.CreateInstance()
$WMIInstance.Key = 'Unique value identifier 5'
$WMIInstance.Value1 = 101
$WMIInstance.Value2 = 'Status Ok'
$WMIInstance.Put()
Solved the problem for Partial Write:
Function Set-WMIPermissionsHC {
Param (
[String]$Namespace = 'CustomCMClasses',
[String]$Class = 'Test',
[String]$Account = 'DOMAIN\Domain users',
[String]$Computer = $env:COMPUTERNAME
)
Function Get-Sid {
Param (
$Account
)
$ID = New-Object System.Security.Principal.NTAccount($Account)
Return $ID.Translate([System.Security.Principal.SecurityIdentifier]).toString()
}
$SID = Get-Sid $Account
$SDDL = "A;CI;CCSWWP;;;$SID"
$DCOMSDDL = "A;;CCDCRP;;;$SID"
$Reg = [WMICLASS]"\\$Computer\root\default:StdRegProv"
$DCOM = $Reg.GetBinaryValue(2147483650,'software\microsoft\ole','MachineLaunchRestriction').uValue
$Security = Get-WmiObject -ComputerName $Computer -Namespace "root\$Namespace" -Class __SystemSecurity
$Converter = New-Object System.Management.ManagementClass Win32_SecurityDescriptorHelper
$BinarySD = #($null)
$Result = $Security.PsBase.InvokeMethod('GetSD', $BinarySD)
$OutSDDL = $Converter.BinarySDToSDDL($BinarySD[0])
$OutDCOMSDDL = $Converter.BinarySDToSDDL($DCOM)
$NewSDDL = $OutSDDL.SDDL += '(' + $SDDL + ')'
$NewDCOMSDDL = $OutDCOMSDDL.SDDL += '(' + $DCOMSDDL + ')'
$WMIbinarySD = $Converter.SDDLToBinarySD($NewSDDL)
$WMIconvertedPermissions = ,$WMIbinarySD.BinarySD
$DCOMbinarySD = $Converter.SDDLToBinarySD($NewDCOMSDDL)
$DCOMconvertedPermissions = ,$DCOMbinarySD.BinarySD
$Result = $Security.PsBase.InvokeMethod('SetSD', $WMIconvertedPermissions)
$Result = $Reg.SetBinaryValue(2147483650,'software\microsoft\ole','MachineLaunchRestriction', $DCOMbinarySD.binarySD)
Write-Verbose 'WMI Permissions set'
}
Thanks to this blog and the Microsoft blog for explaining the permissions