Running PowerShell scripts on clients

Good morning Don.

First of all I would like to start of with saying a big thank you for your work on helping me learn and understand PowerShell. The PowerShell Nuggets helped me a lot and some other stuff (PS Summits) on YouTube was great to. Thanks to you and Jeffrey I learned a new skill and I’m now the only scripting guy in the company.

Because I didn’t know where to ask my question, as it is quite general, I post it here. I’ll try to summarize the best I can and start with describing the use case.

Situation:
Our users have in MS Outlook PST-files mapped. Sometimes from the network (what we don’t want) and sometimes from a local drive on the laptop (what we prefer).

Approach:
The ideal situation would be that we make an inventory first, so we check on the client where the PST-files are stored, send the local paths to the SQL database together with the host name and the SamAccountName. In a later fase we will then decide to fix this by moving them manually (service desk job) or automating it. And of course when it’s all done keep an eye on things with some kind of monitoring.

I am fully capable of writing these scripts that can do the job. We also have an SQL database running that already has some collected data stored from other PS scripts we’re running.

The problem:
We use one service account for all our scripting tasks. On servers this is not an issue as we manage these in country and can manage their permissions. On clients (workstations/laptops) we don’t have permissions with our service account. So we asked group IT (of the mother company) to provide us permissions on the client for the script account. Unlucky for us, this isn’t accepted but they suggested a workaround.

The suggested workarounds for running scripts on the clients are to use the SCCM Client which is installed on all clients and configure a ‘Configuration item’ and a ‘Configuration baseline’ as shown here. This uses the SYSTEM account of the machine or the credentials from the user but it’s a lot of hassle to set up. So they came to the idea of using the SCCM Software library to deploy the script as a package.

My question:
Is this the best way to go? Because now we want to collect PST file locations but next week the boss might ask other stuff from the clients. So a flexible solution would be best. On top of this I was thinking that this might be something for DSC? I’m not really using it for the moment, but do you think that DSC is the best way to go? Regarding permissions… I just might have to give ‘domain users’ write on that one table so we can collect this info from the clients in SQL. Or is this to simple/dangerous?

Any tips you might have are really appreciated. I don’t want to start of something that isn’t thought trough properly. Thank you for your help.

Hi Brecht,

I’m sure others will give you a comparison of DSC and SCCM’s Desired Configuration Management (DCM). My 2 cents . . .

I can see the logic in using SCCM for sending the scripts out there. Providing your Group IT give you access, SCCM will give you detailed reporting on where the script ran, where it failed and where it has yet to run.

But if you’re thinking of expanding into monitoring drift you will also need access SCCM’s DCM and all of its reports.

It seems to me that they would soon have to give you pretty extensive access to SCCM after a while and as a consequence the workstations. Which of course is what they were trying to avoid.

Maybe you could propose compromise situation by allowing them centrally control a local admin account you use via GPO Prefs.

Michael

I’m not sure I’d turn to DSC as a means of collecting files or data from clients. DSC is about maintaining a desired configuration on a node, not performing arbitrary ad-hoc tasks - I think you’d quickly start running into problems and limitations. SCCM, on the other hand, is specifically designed to collect inventory information, and it seems like that’s what you’re trying to do.

Thank you Michael, Don for your feedback. Really appreciate it!

After playing with SCCM’s Compliance Settings it does indeed seem to be the tool for the job. The only real annoyance is on how to have it pas variables from the ‘Discovery script’ to the ‘Remediation script’. Seems to be it’s not accepting objects but only strings. There might even be a way to store values in the SCCM database in one way or another… Hmmmm… There’s a challenge waiting for me :wink:

Anyhow, this is out of scope for you guys.So thanks again! Really great blog with great people here :slight_smile:

Brecht

What values are you trying to return from the Detection script to use in the Remediation script? This might not be the best approach and is probably going to be a bit confusing. I usually just use a return statement to determine if the condition is met. Something like this:

    If (Test-Path 'C:\Users\Public\TestFile')
    {Return 0}

    Else {Return 1}

I then set my compliance rule that all machines must Return 0. If not, I then run the remediation script. Keeping it simple has worked best for me and I only look for (and fix) one thing in each of my Compliance/Remediation scripts. I’m curious to see your approach and what issues you are running into.

Good afternoon Ed and thanks for your post.

I’m trying to have the ‘Discovery script’ pass on some found values to the ‘Remediation script’. This should be possible as it says in the Configuration Items Properties the following:

Remediation script (optional): Specify the script to remediate noncompliant setting values found on client devices. Configuration Manager passes the noncompliant value to the script as a parameter.

You can see here another example of someone passing on values to the SCCM report.

So, without further ado, here are my test scripts for this.

Discovery script

$Compliance = ''
$Paths = Get-ChildItem -Path 'C:\Users\me\Downloads\Input_Test' | Select -ExpandProperty FullName
if ($Paths) {
    $Paths | ForEach-Object {
        $Compliance += "$_`n"
    }
}
else {
    $Compliance = 'Compliant'
}
$Compliance

Remediation script

Param (
    [String[]]$Path
)
$Path | Out-File C:\Users\me\Downloads\Log_Test\Paths.txt -Encoding utf8
Get-ChildItem 'C:\Users\me\Downloads\Input_Test' -Recurse | Remove-Item -Force

New-EventLog -LogName Application -Source SCCMCompliance
Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Information -EventID 501 -Message “We removed paths: $Path

The problem is, it’s always putting ‘Compliant’ in the output file. And when there are files in the folder they get deleted correctly but the parameter ‘$Path’ is not filled from the ‘Discovery script’.

So what it looks like you want to check is if the folder is empty (Compliant) or if the folder has files in it (Non-Compliant)?

It doesn’t really look like you need any parameters passed from one script to the other. If the paths stay the same, you can just include that in your remediation script.

The example in the link only shows a detection script, and not a remediation script, so it only has half of the equation. I’m not exactly sure what it means by passing the non-compliant value to the remediation script or how to capture that value (and my Google-Fu has failed me).

I would try something like this for the remediation script:

$path = Get-ChildItem -Path 'C:\Users\me\Downloads\Input_Test' 
$path | Out-File C:\Users\me\Downloads\Log_Test\Paths.txt -Encoding utf8

Get-ChildItem $path -Recurse | Remove-Item -Force

New-EventLog -LogName Application -Source SCCMCompliance
Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Information -EventID 501 -Message “We removed paths: $Path

If the path is different somehow, you would want to set it to a variable and remove it in the remediation script. This accomplishes the same thing, but isn’t relying on the detection script.

I’m by no means an expert in SCCM, and there may be a better approach, but this is what worked for me.Start there and see if that works for you.

Good morning Ed

Thanks again for you help, I really appreciate your feedback. You are right, I can just as well collect the info again in the ‘Remediation script’ as you suggest with the ‘Get-ChildItem’ CmdLet.

The thing is that I made this code to just test if the value can be passed from the ‘Discovery script’ to the ‘Remediation script’. So it’s actually a non real life example. But yes, your workaround would be the way to go. But I was more looking after the passing on from out put of script 1 to script 2.

Just like you, my Google-fu left me in the dark to :frowning:

Values are not passed from one script to the next, see this answer here.

Glad to help when I can :slight_smile:

Thanks for that link. The wording is a bit confusing, but I guess we weren’t the first ones to try this either. It took me a few tries to get my detection and remediation scripts working correctly. But when they do, it’s a very powerful tag-team.

I actually wish there were a few more baselines I could set. It’s really cool having things fix themselves.

Absolutely true! Wonderful tool it is when it all works. But I’m still in the process of figuring it all out. What I noticed already is the following:

  • When the ‘Discovery script’ finds multiple paths and had these paths in its output the ‘Remediation script’ is also launched multiple times (for each path once, but the path itself is not passed on as a parameter like we already figured out)

It’s great to see click the button ‘View Report’ on the client (Control panel > Configuration Manager > Configurations) and actually see all the paths found. I think this could come in handy for reporting later on.

The only thing that is a real showstopper for me at this moment is the ‘Remediated Value’, you can find this in the same report. It’s always saying ‘Compliant’ while it actually should say ‘NonCompliant’. But I can’t seem to figure that part out yet.

So in short, when the ‘Remediation script’ fails to correct something, you check in the end to see if it’s corrected, when it’s not, the SCCM Configuration Manager should show in the report ‘NonCompliant’. Sadly… It’s always saying ‘Compliant’ even when it isn’t fixed… Bit lost here for now.

If you want to test it to, here is my code:

Discovery script


$Compliance = ''

$Paths = Get-ChildItem -Path 'C:\Users\gijbelsb\Downloads\Input_Test' | Select -ExpandProperty FullName
New-EventLog -LogName Application -Source SCCMCompliance
if ($Paths) {
    #$Paths | ForEach-Object {
    #    $Compliance += "$_`n"
    #}
    $Compliance = $Paths
    Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Warning -EventID 1 -Message “Discovery script: Not compliant”
}
else {
    $Compliance = 'Compliant'
    Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Information -EventID 0 -Message “Discovery script: Compliant”
}

$Compliance

Remediation script

Function Test-CaseHC {
    $Paths = Get-ChildItem -Path 'C:\Users\gijbelsb\Downloads\Input_Test' | Select -ExpandProperty FullName

    if ($Paths) {
        Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Warning -EventID 1 -Message “Remediation script: Not compliant $Paths”
        $Paths
    }
    else {
        Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Information -EventID 0 -Message “Remediation script: Compliant”
        'Compliant'
    }
}

$Test = Test-CaseHC

if ($Test -ne 'Compliant') {
    Write-EventLog -LogName Application -Source SCCMCompliance -EntryType Information -EventID 2 -Message “Remediation script: Trying to fix $Test”
    $Now = (Get-Date).ToString('yyyy-MM-dd HH.mm.ss')

    "Remediation log", $Test | Out-File "C:\Users\gijbelsb\Downloads\Log_Test\Paths $Now.txt" -Encoding utf8 -Append
# Don't fix to see if SCCM flags it as 'NonCompliant'
#    Get-ChildItem 'C:\Users\gijbelsb\Downloads\Input_Test' -Recurse | Remove-Item -Force
}

# Check again for output to SCCM
Test-CaseHC

This blog says that the output of ‘Compliant’ and ‘NonCompliant’ needs to be in the ‘Remediation script’ to. Hmmm…

We have reported this to Microsoft and opened a question on StackOverflow and one on the Microsoft support forum for extra feedback. This is a really annoying issue…