Monday, November 8, 2021

 Installing and Bootstraping an SCCM task sequence during AutoPilot


log-it -message "Starting SCCM Install Script" -component "AP" -path "C:\Temp\" -logname "Company_AP.log"

$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path

cd $ScriptDir

log-it -message "Working dir is now $ScriptDir" -component "AP" -path "C:\Temp\" -logname "Company_AP.log"


New-Item -Path C:\ -Name "Temp" -ItemType Directory -ErrorAction SilentlyContinue

New-Item -Path C:\Temp -Name "SCCM" -ItemType Directory -ErrorAction SilentlyContinue


log-it -message "PS Policy is now $Policy" -component "AP" -path "C:\Temp\" -logname "Company_AP.log"


Function log-it {

param( $message, $component, $path, $thread = 0, $file = 0 )


 


[string]$time = Get-Date -format "HH:mm:ss.fff+300"

[string]$date = Get-Date -Format "MM-dd-yyyy"


 


$a = "<![LOG["

$b = "]LOG]!>"

$carrot = "<"

$closecarrot = ">"

$c = "time=""$time"" date=""$date"" component=""$component"" context="""" type=""1"" thread=""$thread"" file=""$file"""

$logentry =  $a+$message+$b+$carrot+$c+$closecarrot

#Add-Content -Path c:\temp\SCCM-Install.log -Value $logentry


 


Add-Content -Path $path -Value $logmessage


 


}


log-it -message "Working directory is now $scriptdir" -component "AP-SCCM-Install" -path "C:\Temp\" -logname "Company_AP.log"

$dir = dir

log-it -message $dir -component "AP-SCCM-Install" -path "C:\Temp\" -logname "Company_AP.log"



Copy-Item .\ccmsetup.exe c:\temp\SCCM 

Copy-Item .\CMTrace.exe c:\temp




$script=@'

$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path

cd $ScriptDir


#If SCCM is installed AND the AutoPilot TS ran

if( (get-service ccmexec -ErrorAction SilentlyContinue) -and (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").tsname -ne $null)

{

    Disable-ScheduledTask AutoPilotCMClientInstall

}


#TS hasn't ran or didn't finish

else

{

    #Checks if the user is logged in and in an active session

    function Query-User

    {

        $users = (((quser) -ireplace '\s{2,}',',' | ConvertFrom-Csv).username).replace(">","")

        if($users -eq "defaultuser0")

        {

            return $false

        }


        else

        {

            return $true

        }

    }

       

    

    #Checks to make sure machine is connected to VPN

    function Check-VPN

    {

        if ((Test-Connection $localServer -Count 1 -Quiet) -eq $false)

        {

            return $false

        }


        else

        {

            return $true

        }

    }



    $isOnline = Check-VPN

    $isUserLoggedIn = Query-User


    While(($isOnline -eq $false) -or ($isUserLoggedIn -eq $false))

    {

        $isOnline = Check-VPN

        $isUserLoggedIn = Query-User

    

        sleep -Seconds 5


    }


    

    #If TSManager.exe is not running then the TS is not actively running. Okay to install SCCM

    if((ps TSManager -ErrorAction SilentlyContinue) -eq $null)

    {

        Start-Process c:\temp\SCCM\ccmsetup.exe -argumentlist  "/noCRLCheck /mp:CompanyCMG.CLOUDAPP.NET CCMHOSTNAME=CompanyCMG.CLOUDAPP.NET/CCM_Proxy_MutualAuth/72057594037958338 SMSSiteCode=XXX PROVISIONTS=C0220AC4 /forceinstall" 

    }



    sleep -Seconds 600


    #If SCCM is installed AND the AutoPilot TS ran

    if( (get-service ccmexec -ErrorAction SilentlyContinue) -and (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").tsname -ne $null)

    {

        Disable-ScheduledTask AutoPilotCMClientInstall

    }


}


'@


log-it -message "Creating C:\Temp\SCCM\AutoPilotCMClientInstall.ps1 script." -component "AP-SCCM-Install" -path "C:\Temp\" -logname "Company_AP.log"

Add-Content C:\Temp\SCCM\AutoPilotCMClientInstall.ps1 -Value $script



#Checks if SCCM is already installed. If it is, it will not create the scheduled task.

$isInstalled = gwmi win32_product | ? {$_.Name -eq "Configuration Manager Client"}


if($isInstalled -eq $null)

{

    log-it -message "Creating Scheduled Task." -component "AP-SCCM-Install" -path "C:\Temp\" -logname "Company_AP.log"

    

    $A = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-Ex Bypass -file C:\Temp\SCCM\AutoPilotCMClientInstall.ps1"

    #$T = New-ScheduledTaskTrigger -AtLogOn


    $T = @(

    $(New-ScheduledTaskTrigger -AtLogOn),

    $(New-ScheduledTaskTrigger -Once -At (get-date) -RepetitionInterval (New-TimeSpan -Minutes 1) )

    )


    $P =  New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest

    $S = New-ScheduledTaskSettingsSet –AllowStartIfOnBatteries –DontStopIfGoingOnBatteries -DontStopOnIdleEnd

    $D = New-ScheduledTask -Action $A -Principal $P -Trigger $T -Settings $S

    Register-ScheduledTask AutoPilotCMClientInstall -InputObject $D

}



#Enables UAC visibility through MSRA / Quick Assist

Set-ItemProperty -path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -name PromptOnSecureDesktop -Value 0 -ErrorAction SilentlyContinue







#Detects if script was successful

$task = (Get-ScheduledTask AutoPilotCMClientInstall -ErrorAction SilentlyContinue).state


if ( ($task -eq "Ready") -or ($task -eq "Running") -or (test-path C:\Windows\CCM\CcmExec.exe) )

{

    log-it -message "SCCM is either detected or the installation TS is enabled and will install it." -component "AP" -path "C:\Temp\" -logname "Company_AP.log"

}


else

{

    log-it -message "Didn't detect task!" -component "AP" -path "C:\Temp\" -logname "Company_AP.log"


}


 

Script to get the current user's AD details.

<#
Script will get the currently logged in user and then search for their AD attributes via ADSI.
The attributes are written to a csv file in c:\temp to be used later. They cannot be directly written to TS variables since this step has to be ran as a user account and it would have its own variable store.
#>

#Step needs to run as an AD account to pull info. Currently runs as _sccmsvc1


$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment

#Get logged in user
$user = (Get-WmiObject -Class Win32_Process -Filter 'Name="explorer.exe"').GetOwner().User

#Get AD User attributes
$searcher = [adsisearcher]"(samaccountname=$user)"
$userTitle = $searcher.FindOne().Properties.title
$userDeptNum = $searcher.FindOne().Properties.extensionattribute5
$userDeptName = $searcher.FindOne().Properties.department
$userLocation = $searcher.FindOne().Properties.extensionattribute7


$userObject = New-Object psobject -Property @{
"currentUserADID" = "$user"
"currentUserTitle" = "$userTitle"
"currentUserDeptNum" = "$userDeptNum"
"currentUserDeptName" = "$userDeptName"
"currentUserLocation" = "$userLocation"
}

$userObject | export-csv c:\temp\userAttributes.csv -NoTypeInformation

 Detect OOBE - Script to see if an AutoPilot device is in OOBE


$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path -ErrorAction Ignore
cd $ScriptDir -ErrorAction Ignore


$LoggedOnUser = Get-WmiObject win32_computersystem | select Username -ErrorAction Ignore
$user = $LoggedOnUser.Username

$count = 0
while($user -like "")
{
$LoggedOnUser = Get-WmiObject win32_computersystem | select Username -ErrorAction Ignore
$user = $LoggedOnUser.Username
$count++
sleep -Seconds 5

}


If($LoggedOnUser.Username -like "*defaultuser0")
{

Write-Output "OOBE"
exit 0

}


else
{

exit 12345

}

Thursday, October 28, 2021

PowerShell Code to add a PC to an Azure Group



<#
.Synopsis
Add Computers to Azure AD Group
.DESCRIPTION
Add Computers to Azure AD Group.
.EXAMPLE
Create a txt file with the netbios names of devices you want to add. The script invokes a file picker to allow you to choose the file.
.INPUTS
Inputs to this cmdlet (if any) None
.OUTPUTS
Output from this cmdlet (if any) Console
.NOTES
General notes
.COMPONENT
AzureAD#>
###################################################################################
# Adjust these variables accordingly... #
###################################################################################
$azgroup = "BitLocker Settings Assignment Group"

####################################################################################lets check to see if we have the Azure AD module installed...

if (Get-Module -ListAvailable -Name Azuread) {
Write-Host "AzureAD Module exists, loading"
Import-Module Azuread
}


else {
#no module, does user hae admin rights?
Write-Host "AzureAD Module does not exist please install`r`n with install-module azuread" -ForegroundColor Red
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
[Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Host "Insufficient permissions to install module. Please run as an administrator and try again." -ForegroundColor DarkYellow
return(0)
}
else {
Write-Host "Attempting to install Azure AD module" -ForegroundColor Cyan
Install-Module AzureAD -Confirm:$False -Force
}
}# OK, lets pick the file..
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
InitialDirectory = [Environment]::GetFolderPath('Desktop')
Filter = 'Documents (*.txt)|*.txt|TextFile (*.txt)|*.txt'
}
$null = $FileBrowser.ShowDialog()
$machines = get-content $FileBrowser.FileName
#ok, if we got here, we must have the Azure AD module installed, lets connect...
Connect-AzureAD
write-host "Getting Object ID of group.." -ForegroundColor Green
$objid = (get-azureadgroup -Filter "DisplayName eq '$azgroup'" ).objectid
write-host "Getting group members (We dont want duplicates!).." -ForegroundColor Cyan
$members = Get-AzureADGroupMember -ObjectId $objid -all $true | select displayname
foreach ($machine in $machines) {
$refid = Get-AzureADDevice -Filter "DisplayName eq '$machine'"
Write-host "Adding " $refid.displayname -ForegroundColor Cyan
Add-AzureADGroupMember -ObjectId $objid -RefObjectId $refid.objectid
}

Wednesday, August 26, 2020

 This bit of PowerShell code writes log entries that can be read with CMTrace


<#

.SYNOPSIS

  Function writes to log files so CMTrace can read them

.DESCRIPTION

  Function writes to log files so CMTrace can read them

.PARAMETER message

    The message you want to write to the log

.PARAMETER component

    The component that wrote to the log

.PARAMETER path

    The path of the log file, written like "c:\temp"

.PARAMETER logname

    The name of the log, wirrent like "test.log"

.INPUTS

  None

.OUTPUTS

  Log file stated in the commandlet

.NOTES

  Version:        1.0

  Author:         Jeffery Field (Jefield)

  Creation Date:  July 13th 2020

  Purpose/Change: Initial script development

  

.EXAMPLE

  log-it -message "message testing" -component "One-X-Script" -path "C:\Temp\" -logname "test.log"

#>


Function log-it {

[CmdletBinding()]

param(

[Parameter(Mandatory=$true)]

[string]$message,


[Parameter(Mandatory=$true)]

[string]$component,


[Parameter(Mandatory=$true)]

[string]$path,


[Parameter(Mandatory=$true)]

[string]$logname,


$thread = "0", $file = "N/A"


)


#Tests the folder path and creates it. If it can't create it we stop the script

$Pathexists = test-Path $path

if($Pathexists -eq $false){


    try{

    New-Item -Path $path -ItemType directory -Force

       }catch{

        Write-Host "unable to make log path"

        Start-Sleep -Seconds 15

        exit

        }

}




[string]$time = Get-Date -format "HH:mm:ss.fff+300"

[string]$date = Get-Date -Format "MM-dd-yyyy"


$a = "<![LOG["

$b = "]LOG]!>"

$carrot = "<"

$closecarrot = ">"

$c = "time=""$time"" date=""$date"" component=""$component"" context="""" type=""1"" thread=""$thread"" file=""$file"""

$logentry =  $a+$message+$b+$carrot+$c+$closecarrot

Add-Content -Path $path\$logname -Value $logentry


}

Thursday, June 27, 2019

Clear IE Data if the user is inactive


This script asks the user if they are active and if they say no or leave it for 30 seconds the script then clears IE data.

$logpath = "$env:systemdrive\temp\"

$date = Get-Date
Add-Content $logpath\IdleTimer.log "Strarting Script - $date"

#set the volume to 90% and unmute
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume
{
    // f(), g(), ... are unused COM method slots. Define these if you care
    int f(); int g(); int h(); int i();
    int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
    int j();
    int GetMasterVolumeLevelScalar(out float pfLevel);
    int k(); int l(); int m(); int n();
    int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
    int GetMute(out bool pbMute);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice
{
    int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator
{
    int f(); // Unused
    int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio
{
    static IAudioEndpointVolume Vol()
    {
        var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
        IMMDevice dev = null;
        Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
        IAudioEndpointVolume epv = null;
        var epvid = typeof(IAudioEndpointVolume).GUID;
        Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
        return epv;
    }
    public static float Volume
    {
        get { float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v; }
        set { Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty)); }
    }
    public static bool Mute
    {
        get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
        set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
    }
}
'@
Try{
[audio]::Mute = $false
[audio]::Volume  = 0.9
}Catch{
    $date = Get-Date
    Add-Content $logpath\IdleTimer.log "Could not turn up the volume - $date"
}

#Some code I don't understand to get last user input time
Add-Type @'
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    namespace PInvoke.Win32 {
        public static class UserInput {
            [DllImport("user32.dll", SetLastError=false)]
            private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
            [StructLayout(LayoutKind.Sequential)]
            private struct LASTINPUTINFO {
                public uint cbSize;
                public int dwTime;
            }
            public static DateTime LastInput {
                get {
                    DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount);
                    DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks);
                    return lastInput;
                }
            }
            public static TimeSpan IdleTime {
                get {
                    return DateTime.UtcNow.Subtract(LastInput);
                }
            }
            public static int LastInputTicks {
                get {
                    LASTINPUTINFO lii = new LASTINPUTINFO();
                    lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
                    GetLastInputInfo(ref lii);
                    return lii.dwTime;
                }
            }
        }
    }
'@

<#loop indefinitely to check how long it's been for user input every 2 minutes.
If the user hasn't touched the mouse or keyboard for longer than 2 minutes we pop up a window.
If the user doesn't interact with the window in 30 seconds it self closes and clears IE
If the user click Yes it just starts over.
#>
While($true){
    $date = Get-Date
    Write-Host ("Last input " + [PInvoke.Win32.UserInput]::LastInput)
    Write-Host ("Idle for " + [PInvoke.Win32.UserInput]::IdleTime)
    Start-Sleep -Seconds 120
    $idleTime = [PInvoke.Win32.UserInput]::IdleTime
    [int64]$MINidleTimeINT = $idleTime.Minutes
    Add-Content $logpath\IdleTimer.log "User is inactive for $MINidleTimeINT seconds"

    [int64]$FileSize = "{0:N2}" -f ((Get-ChildItem C:\temp\IdleTracker.Log -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)

    If($FileSize -ge 1){Remove-Item $logpath\IdleTimer.log}

    If($MINidleTimeINT -ge 2){
        [console]::beep(2000,500)
        $sh = New-Object -ComObject "Wscript.Shell"
        $intButton = $sh.Popup("Are you still there?",30,"Idle Timer",4)
            If($intButton -eq 6){
                Write-Host "user is still active"
                Add-Content $logpath\IdleTimer.log "User is active - $date"
                }else{
                    Write-host "User is inactive"
                    Add-Content $logpath\IdleTimer.log "User is inactive - Clearing Cache"
                    RunDll32.exe InetCpl.cpl, ClearMyTracksByProcess 255}
}
}

Monday, December 3, 2018

Create SCCM User collections from a CSV File

This PowerShell code will create SCCM user collections from a CSV file.


The CSV file looks like this:

ADGroup
NickName
Code
City
Location-3254
LOC-A
3254
Austin
Location-1499
LOC-B
1499
Baltimore
Location-3269
LOC-I
3269
Indianapolis
Location-1395
LOC-K
1395
Knoxville
Location-1999
LOC-M
1999
Madison
Location-1998
LOC-W
1998
Waco


#Import AD Groups from CSV
$CSV = Import-Csv c:\temp\CSC.csv

foreach($item in $csv)
    {
    "ADGroup = $($item.ADGroup) and NickName = $($item.NickName) and Code = $($item.Code) and City = $($item.City)"
    $ADGroups += $item
    }

foreach($ADGroup in $ADGroups){
#Get a random time and day of the week. This is so we don't create a ton of groups that update at the time and crash SCCM
$RandomDay = Get-Random -InputObject Monday, Tuesday, Wednesday, Thrusday, Friday, Saturday, Sunday

#Had to do a random hour like this join didn't like intergers
$RandomHour = Get-Random -InputObject 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12

#Variables we later use to format the date and time in a format that SCCM likes
$TimeSuffix = ":00"
$RandomAMPM = Get-Random -InputObject AM, PM
$Today = get-date -UFormat "%d/%m/%Y"
$space = " "

#join all  of the random stuff together
$TimeAndDate = -join($Today, $space, $RandomHour, $TimeSuffix, $space, $RandomAMPM)
#Formats the date and time like this 03/12/2018 6:00 AM

#Creates a schedule for the collection to update
$Schedule = New-CMSchedule -Start "$TimeAndDate" -DayOfWeek $RandomDay -RecurCount 1

#Creates the User collection
New-CMUserCollection -Name "Users in AD Group $ADGroup.ADGroup - $ADGroup.Nickname" -LimitingCollectionName "All Users and User Groups" -RefreshSchedule $Schedule -RefreshType Periodic

#Adds the query rule to the collection
Add-CMUserCollectionQueryMembershipRule -CollectionName "Users in AD Group $ADGroup.ADGroup" -QueryExpression "select *  from  SMS_R_User where SMS_R_User.UserGroupName = 'LOWES\\$ADGroup.ADGroup'" -RuleName "Users in $ADGroup.ADGroup"

}