Powershell - Loop Install of Available Software Updates (SCCM) - windows

I have the below script which I am using to run on critical desktop clients to install all available updates (quarterly) that have been deployed by SCCM.
As some deployed updates only become available when other dependent updates have been installed the script is stopping before the reboot.
I ideally want it to loop and continue to install all available updates until all have installed and then proceed to automatically reboot.
Any ideas?
Add-Type -AssemblyName PresentationCore, PresentationFramework
switch (
[System.Windows.MessageBox]::Show(
'This action will download and install critical Microsoft updates and may invoke an automatic reboot. Do you want to continue?',
'WARNING',
'YesNo',
'Warning'
)
) {
'Yes'
{
Start-Process -FilePath "C:\Windows\CCM\ClientUX\scclient.exe" "softwarecenter:Page=InstallationStatus"
$installUpdateParam = #{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdatesManager'
MethodName = 'InstallUpdates'
}
$getUpdateParam = #{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdate'
Filter = 'EvaluationState < 8'
}
[ciminstance[]]$updates = Get-CimInstance #getUpdateParam
if ($updates) {
Invoke-CimMethod #installUpdateParam -Arguments #{ CCMUpdates = $updates }
while(Get-CimInstance #getUpdateParam){
Start-Sleep -Seconds 30
}
}
$rebootPending = Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending
if ($rebootPending.RebootPending){
Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName RestartComputer
}
'No'
# Exit-PSSession
}
}

You may loop indefinitely to start the process and stop only when $updates is $null or empty.
while($true) {
Start-Process ...
[ciminstance[]]$updates = Get-CimInstance #getUpdateParam
if ($updates) {
Invoke-CimMethod #installUpdateParam -Arguments #{ CCMUpdates = $updates }
while(Get-CimInstance #getUpdateParam){
Start-Sleep -Seconds 30
}
}
else {
break;
}
}

Related

Using Powershell to get a list of users who have logged into a machine in the past year

We have a few computers that are used in a lab for processing and users can log in directly on-site or via remote desktop. We are trying to clean up the machines and decided to remove user folders for those who have not logged on in the past year. I am able to use windows event viewer to find the information, but I haven't figured out a way to export the information I need.
I found this script which seems to do exactly what I need, except I'm getting the following error when I run it: https://github.com/adbertram/Random-PowerShell-Work/blob/master/ActiveDirectory/Get-UserLogonSessionHistory.ps1
PS C:\Users\vmc\Documents> .\userevents.ps1
Get-WinEvent : Could not retrieve information about the Security log. Error: Attempted to perform an unauthorized
operation..
At C:\Users\vmc\Documents\userevents.ps1:48 char:29
+ ... ($events = Get-WinEvent -ComputerName $computer -LogName $logNames - ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], Exception
+ FullyQualifiedErrorId : LogInfoUnavailable,Microsoft.PowerShell.Commands.GetWinEventCommand
Get-WinEvent : There is not an event log on the BIGBERTHA computer that matches "Security".
At C:\Users\vmc\Documents\userevents.ps1:48 char:29
+ ... ($events = Get-WinEvent -ComputerName $computer -LogName $logNames - ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Security:String) [Get-WinEvent], Exception
+ FullyQualifiedErrorId : NoMatchingLogsFound,Microsoft.PowerShell.Commands.GetWinEventCommand
C:\Users\vmc\Documents\userevents.ps1 : A positional parameter cannot be found that accepts argument '.'.
At line:1 char:1
+ .\userevents.ps1
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [userevents.ps1], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,userevents.ps1
I do have a log called 'Security' when I look at the Windows Event Viewer, so I'm not sure why I can't query this log?
Thank you for any help- suggestions to get this script to run or another way to compile this list are very much appreciated!
Script from the link (saved to userevents.ps1, called from powershell above)
<#
.SYNOPSIS
This script finds all logon, logoff and total active session times of all users on all computers specified. For this script
to function as expected, the advanced AD policies; Audit Logon, Audit Logoff and Audit Other Logon/Logoff Events must be
enabled and targeted to the appropriate computers via GPO or local policy.
.EXAMPLE
.PARAMETER ComputerName
An array of computer names to search for events on. If this is not provided, the script will search the local computer.
.INPUTS
None. You cannot pipe objects to Get-ActiveDirectoryUserActivity.ps1.
.OUTPUTS
None. If successful, this script does not output anything.
#>
[CmdletBinding()]
param
(
[Parameter()]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $Env:COMPUTERNAME
)
try {
#region Defie all of the events to indicate session start or top
$sessionEvents = #(
#{ 'Label' = 'Logon'; 'EventType' = 'SessionStart'; 'LogName' = 'Security'; 'ID' = 4624 } ## Advanced Audit Policy --> Audit Logon
#{ 'Label' = 'Logoff'; 'EventType' = 'SessionStop'; 'LogName' = 'Security'; 'ID' = 4647 } ## Advanced Audit Policy --> Audit Logoff
#{ 'Label' = 'Startup'; 'EventType' = 'SessionStop'; 'LogName' = 'System'; 'ID' = 6005 }
#{ 'Label' = 'RdpSessionReconnect'; 'EventType' = 'SessionStart'; 'LogName' = 'Security'; 'ID' = 4778 } ## Advanced Audit Policy --> Audit Other Logon/Logoff Events
#{ 'Label' = 'RdpSessionDisconnect'; 'EventType' = 'SessionStop'; 'LogName' = 'Security'; 'ID' = 4779 } ## Advanced Audit Policy --> Audit Other Logon/Logoff Events
#{ 'Label' = 'Locked'; 'EventType' = 'SessionStop'; 'LogName' = 'Security'; 'ID' = 4800 } ## Advanced Audit Policy --> Audit Other Logon/Logoff Events
#{ 'Label' = 'Unlocked'; 'EventType' = 'SessionStart'; 'LogName' = 'Security'; 'ID' = 4801 } ## Advanced Audit Policy --> Audit Other Logon/Logoff Events
)
## All of the IDs that designate when user activity starts
$sessionStartIds = ($sessionEvents | where { $_.EventType -eq 'SessionStart' }).ID
## All of the IDs that designate when user activity stops
$sessionStopIds = ($sessionEvents | where { $_.EventType -eq 'SessionStop' }).ID
#endregion
## Define all of the log names we'll be querying
$logNames = ($sessionEvents.LogName | select -Unique)
## Grab all of the interesting IDs we'll be looking for
$ids = $sessionEvents.Id
## Build the insane XPath query for the security event log in order to query events as fast as possible
$logonXPath = "Event[System[EventID=4624]] and Event[EventData[Data[#Name='TargetDomainName'] != 'Window Manager']] and Event[EventData[Data[#Name='TargetDomainName'] != 'NT AUTHORITY']] and (Event[EventData[Data[#Name='LogonType'] = '2']] or Event[EventData[Data[#Name='LogonType'] = '11']])"
$otherXpath = 'Event[System[({0})]]' -f "EventID=$((#($ids).where({ $_ -ne '4624' })) -join ' or EventID=')"
$xPath = '({0}) or ({1})' -f $logonXPath, $otherXpath
foreach ($computer in $ComputerName) {
## Query each computer's event logs using the Xpath filter
if (-not ($events = Get-WinEvent -ComputerName $computer -LogName $logNames -FilterXPath $xPath)) {
Write-Warning -Message 'No logon events found'.
} else {
Write-Verbose -Message "Found [$($events.Count)] events to look through"
## Set up the output object
$output = [ordered]#{
'ComputerName' = $computer
'Username' = $null
'StartTime' = $null
'StartAction' = $null
'StopTime' = $null
'StopAction' = $null
'Session Active (Days)' = $null
'Session Active (Min)' = $null
}
## Need current users because if no stop time, they're still probably logged in
$getGimInstanceParams = #{
ClassName = 'Win32_ComputerSystem'
}
if ($computer -ne $Env:COMPUTERNAME) {
$getGimInstanceParams.ComputerName = $computer
}
$loggedInUsers = Get-CimInstance #getGimInstanceParams | Select-Object -ExpandProperty UserName | foreach { $_.split('\')[1] }
## Find all user start activity events and begin parsing
#($events).where({ $_.Id -in $sessionStartIds }).foreach({
try {
$logonEvtId = $_.Id
$output.StartAction = #($sessionEvents).where({ $_.ID -eq $logonEvtId }).Label
$xEvt = [xml]$_.ToXml()
## Figure out the login session ID
$output.Username = ($xEvt.Event.EventData.Data | where { $_.Name -eq 'TargetUserName' }).'#text'
$logonId = ($xEvt.Event.EventData.Data | where { $_.Name -eq 'TargetLogonId' }).'#text'
if (-not $logonId) {
$logonId = ($xEvt.Event.EventData.Data | where { $_.Name -eq 'LogonId' }).'#text'
}
$output.StartTime = $_.TimeCreated
Write-Verbose -Message "New session start event found: event ID [$($logonEvtId)] username [$($output.Username)] logonID [$($logonId)] time [$($output.StartTime)]"
## Try to match up the user activity end event with the start event we're processing
if (-not ($sessionEndEvent = #($Events).where({ ## If a user activity end event could not be found, assume the user is still logged on
$_.TimeCreated -gt $output.StartTime -and
$_.ID -in $sessionStopIds -and
(([xml]$_.ToXml()).Event.EventData.Data | where { $_.Name -eq 'TargetLogonId' }).'#text' -eq $logonId
})) | select -last 1) {
if ($output.UserName -in $loggedInUsers) {
$output.StopTime = Get-Date
$output.StopAction = 'Still logged in'
} else {
throw "Could not find a session end event for logon ID [$($logonId)]."
}
} else {
## Capture the user activity end time
$output.StopTime = $sessionEndEvent.TimeCreated
Write-Verbose -Message "Session stop ID is [$($sessionEndEvent.Id)]"
$output.StopAction = #($sessionEvents).where({ $_.ID -eq $sessionEndEvent.Id }).Label
}
$sessionTimespan = New-TimeSpan -Start $output.StartTime -End $output.StopTime
$output.'Session Active (Days)' = [math]::Round($sessionTimespan.TotalDays, 2)
$output.'Session Active (Min)' = [math]::Round($sessionTimespan.TotalMinutes, 2)
[pscustomobject]$output
} catch {
Write-Warning -Message $_.Exception.Message
}
})
}
}
} catch {
$PSCmdlet.ThrowTerminatingError($_)
}
First you got this error:
Attempted to perform an unauthorized
And:
There is not an event log on the BIGBERTHA computer that matches "Security"
This clearly indicates that the account used to run the query against the remote computer does not have the necesssary permission. You can't access the and/or find the Security Log because of insufficient acces rights.
In your 2nd run with the right account and elevated shell you did get further, because you now get the error:
The specified query is invalid
That means you have been able to connect to the remote computer and read the security log but get-winevent could not execute the operation because the query syntax is invalid.
This part of the code builds the filter:
$logonXPath = "Event[System[EventID=4624]] and Event[EventData[Data[#Name='TargetDomainName'] != 'Window Manager']] and Event[EventData[Data[#Name='TargetDomainName'] != 'NT AUTHORITY']] and (Event[EventData[Data[#Name='LogonType'] = '2']] or Event[EventData[Data[#Name='LogonType'] = '11']])"
$otherXpath = 'Event[System[({0})]]' -f "EventID=$((#($ids).where({ $_ -ne '4624' })) -join ' or EventID=')"
$xPath = '({0}) or ({1})' -f $logonXPath, $otherXpath
The scripts works for me... but in the end you do not need the whole functionality, think this will help:
#Query
$XPath = "Event[System[EventID=4624]] and Event[EventData[Data[#Name='TargetDomainName'] != 'Window Manager']] and Event[EventData[Data[#Name='TargetDomainName'] != 'NT AUTHORITY']] and (Event[EventData[Data[#Name='LogonType'] = '2']] or Event[EventData[Data[#Name='LogonType'] = '11']])"
#Get List containing the dnsHostNames of the computers to query
$computer = gc [path]
#Run Query against computers and gather result
$result = #(
foreach ($computer in $computers){
#run query
$events = get-winevent -LogName security -FilterXPath $XPath -ComputerName $computer
#parse events as xml and extract necessary information, return object
$eventobj = #(
foreach ($event in $events){
[xml]$xml = $event.toxml()
$attrsht = [ordered]#{
TimeCreated=$xml.event.system.TimeCreated.SystemTime
eventId=$xml.event.system.eventId
SubjectUserSid=$xml.event.EventData.data[0].'#text'
SubjectUserName=$xml.event.EventData.data[1].'#text'
SubjectDomainName=$xml.event.EventData.data[2].'#text'
TargetUserSid=$xml.event.EventData.data[4].'#text'
TargetUserName=$xml.event.EventData.data[5].'#text'
TargetDomainName=$xml.event.EventData.data[6].'#text'
LogonType=$xml.event.EventData.data[8].'#text'
LogonProcessName=$xml.event.EventData.data[9].'#text'
ipAdress=$xml.event.EventData.data[18].'#text'
}
#return event object
new-object -TypeName psobject -Property $attrsht
}
)
$attrsht = #{
Computer=$computer
Events=$eventobj
}
#return object per computer containing all events
new-object -TypeName psobject -Property $attrsht
}
)
#As the property events is an array you can export it by using json
$result | ConvertTo-Json | set-content [path]
#If you want a csv we have to flattern the array
$result = #(
foreach ($computer in $computers){
#run query
$events = get-winevent -LogName security -FilterXPath $XPath -ComputerName $computer
#parse events as xml and extract necessary information, return object
foreach ($event in $events){
[xml]$xml = $event.toxml()
$attrsht = [ordered]#{
computername=$computer
TimeCreated=$xml.event.system.TimeCreated.SystemTime
eventId=$xml.event.system.eventId
SubjectUserSid=$xml.event.EventData.data[0].'#text'
SubjectUserName=$xml.event.EventData.data[1].'#text'
SubjectDomainName=$xml.event.EventData.data[2].'#text'
TargetUserSid=$xml.event.EventData.data[4].'#text'
TargetUserName=$xml.event.EventData.data[5].'#text'
TargetDomainName=$xml.event.EventData.data[6].'#text'
LogonType=$xml.event.EventData.data[8].'#text'
LogonProcessName=$xml.event.EventData.data[9].'#text'
ipAdress=$xml.event.EventData.data[18].'#text'
}
#return event object
new-object -TypeName psobject -Property $attrsht
}
}
)
$result | export-csv [path] -NoClobber -NoTypeInformation -Delimiter ";"

mount.exe in powershell service not mounting NFS for the user, only current process

I'm using winsw to run a powershell 7 service with user credentials, in order to automatically mount an NFS volume. I can verify the service is running as that user since $env:UserName shows up correctly in the log.
Strangely, when the service runs this command:
mount.exe -o anon,nolock,hard 10.1.132.244:/rendering.dev.firehawkvfx.com X:
The service script can see the contents of the mounted path and that works, but the user in the windows UI session cannot, and the mount doesn't arrive in windows explorer at all. It appears the mount only exists for the process. This must have something to do with the way processes are isolated in windows is my guess.
There are a few components involved in doing this, but at the risk of being verbose the winsw service looks like this:
<service>
<id>myservice</id>
<name>MyService</name>
<description>This service updates Deadline Certificates with Firehawk.</description>
<serviceaccount>
<username>.\REPLACE_WITH_DEADLINE_USER_NAME</username>
<password>REPLACE_WITH_DEADLINE_USER_PASS</password>
<allowservicelogon>true</allowservicelogon>
</serviceaccount>
<env name="FH_DEADLINE_CERTS_HOME" value="%BASE%"/>
<executable>C:\Program Files\PowerShell\7\pwsh.exe</executable>
<startarguments>-NoLogo -ExecutionPolicy Bypass -File c:\AppData\myservice.ps1</startarguments>
<log mode="roll"></log>
</service>
and myservice.ps1 wrapper that runs the NFS mount.exe command (in aws-auth-deadline-pwsh-cert.ps1) looks like this:
#Requires -Version 7.0
Write-Host "Start Service"
# $ErrorActionPreference = "Stop"
function Main {
$Timer = New-Object Timers.Timer
$Timer.Interval = 10000
$Timer.Enabled = $True
$Timer.AutoReset = $True
$objectEventArgs = #{
InputObject = $Timer
EventName = 'Elapsed'
SourceIdentifier = 'myservicejob'
Action = {
try {
$resourcetier = "dev"
Write-Host "Run aws-auth-deadline-cert`nCurent user: $env:UserName"
Set-strictmode -version latest
if (Test-Path -Path C:\AppData\myservice-config.ps1) {
. C:\AppData\myservice-config.ps1
C:\AppData\aws-auth-deadline-pwsh-cert.ps1 -resourcetier $resourcetier -deadline_user_name $deadline_user_name -aws_region $aws_region -aws_access_key $aws_access_key -aws_secret_key $aws_secret_key
}
else {
Write-Warning "C:\AppData\myservice-config.ps1 does not exist. Install the service again and do not use the -skip_configure_aws argument"
}
Write-Host "Finished running aws-auth-deadline-cert"
}
catch {
Write-Warning "Error in service Action{} block"
Write-Warning "Message: $_"
exit(1)
}
}
}
$Job = Register-ObjectEvent #objectEventArgs
Wait-Event
}
try {
Main
}
catch {
Write-Warning "Error running Main in: $PSCommandPath"
exit(1)
}
In case its of interest, I maintain this work ongoing at this github repo - https://github.com/firehawkvfx/firehawk-auth-scripts

Downloading certain files using powershell produce corrupt files

So I have a powershell script that I wrote which crawls through a particular website and downloads all of the software hosted on the site to my local machine. The website in question is nirsoft.net, and I will include the full script below. Anyway, so I have this script that downloads all of the application files hosted on the website, when I notice something odd: while most of the file downloads completed successfully, there are several files that were not downloaded successfully, resulting in a corrupt file of 4KB:
For those of you who are familiar with Nirsoft's software, the tools are very powerful, but also constantly misidentified as dangerous because of the password cracking tools, so my guess as to why this is happening is that, since powershell's If I were to guess as to why this was happening, I would guess that, due to the fact that powershell's "Invoke-webrequest cmdlet" uses Internet Explorer's engine for its core functionality, Internet Explorer is flagging the files as dangerous and refusing to download them, thus causing powershell to fail to download the file. I confirmed this by trying to manually download each of the corrupt files using internet explorer, which marked them all as malicious. However, this is where things get strange. In order to bypass this limitation, I attempted a variety of other methods to download the file within my script, like using a pure dotnet object ( (New-object System.Net.WebClient).DownloadFile("url","file") ) and even some third party command line tools (wget for windows, wget in cygwin, etc), but no matter what I tried, not a single alternative method I used was able to download a non-corrupt file. So what I want to know is if there is a way around this, and I want to know why even third party tools are affected by this. Is there some kind of rule that any scripting tool has to use Internet Explorer's engine in order to connect to the internet or something? Thanks in advance. Oh, and one last thing before I post the script. Below is the url to one of the files that I am having difficulty in downloading via powershell, which you can use to run individual tests rather than the whole script:
enter link description here
And without further ado, here is the script. Thank again:
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
$present = $true
$subdomain = $null
$prods = (Invoke-WebRequest "https://www.nirsoft.net/utils/index.html").links
Foreach ($thing in $prods)
{
If ($thing.Innertext -match "([A-Za-z]|\s)+v\d{1,3}\.\d{1,3}(.)*")
{
If ($thing.href.Contains("/"))
{
}
$page = Invoke-WebRequest "https://www.nirsoft.net/utils/$($thing.href)"
If ($thing.href -like "*dot_net_tools*")
{
$prodname = $thing.innerText.Trim().Split(" ")
}
Else
{
$prodname = $thing.href.Trim().Split(".")
}
$newlinks = $page.links | Where-Object {$_.Innertext -like "*Download*" -and ($_.href.endswith("zip") -or $_.href.endswith("exe"))}
# $page.ParsedHtml.title
#$newlinks.href
Foreach ($item in $newlinks)
{
$split = $item.href.Split("/")
If ($item.href -like "*toolsdownload*")
{
Try
{
Write-host "https://www.nirsoft.net$($item.href)"
Invoke-WebRequest "https://www.nirsoft.net$($item.href)" -OutFile "$env:DOWNLOAD\test\$($split[-1])" -ErrorAction Stop
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
elseif ($item.href.StartsWith("http") -and $item.href.Contains(":"))
{
Try
{
Write-host "$($item.href)"
Invoke-WebRequest $item.href -OutFile "$env:DOWNLOAD\test\$($split[-1])" -ErrorAction Stop
}
Catch
{
Write-Host "$($item.href)" -ForegroundColor Red
}
}
Elseif ($thing.href -like "*/dot_net_tools*")
{
Try
{
Invoke-WebRequest "https://www.nirsoft.net/dot_net_tools/$($item.href)" -OutFile "$env:DOWNLOAD\test\$($split[-1])" -ErrorAction Stop
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
Else
{
Try
{
Write-Host "https://www.nirsoft.net/utils/$($item.href)"
Invoke-WebRequest "https://www.nirsoft.net/utils/$($item.href)" -OutFile "$env:DOWNLOAD\test\$($item.href)" -ErrorAction Stop
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
If ($item.href.Contains("/"))
{
If (!(Test-Path "$env:DOWNLOAD\test\$($split[-1])"))
{
$present = $false
}
}
Else
{
If (!(Test-Path "$env:DOWNLOAD\test\$($item.href)"))
{
$present = $false
}
}
}
}
}
If ($present)
{
Write-Host "All of the files were downloaded!!!" -ForegroundColor Green
}
Else
{
Write-Host "Not all of the files downloaded. Something went wrong." -ForegroundColor Red
}
You have two separate issues.
For anything Defender flags, it doesn't matter if you save it to disk with this or that. You could simply add an exclusion for the directory in Defender.
The other issue is pointed out by Guenther, you need to provide a referrer at least on some of the downloads. With the following changes I was able to download them all.
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
$present = $true
$subdomain = $null
$path = c:\temp\downloadtest\
New-Item $path -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
Add-MpPreference -ExclusionPath $path
$prods = (Invoke-WebRequest "https://www.nirsoft.net/utils/index.html").links
Foreach ($thing in $prods)
{
If ($thing.Innertext -match "([A-Za-z]|\s)+v\d{1,3}\.\d{1,3}(.)*")
{
If ($thing.href.Contains("/"))
{
}
$page = Invoke-WebRequest "https://www.nirsoft.net/utils/$($thing.href)"
If ($thing.href -like "*dot_net_tools*")
{
$prodname = $thing.innerText.Trim().Split(" ")
}
Else
{
$prodname = $thing.href.Trim().Split(".")
}
$newlinks = $page.links | Where-Object {$_.Innertext -like "*Download*" -and ($_.href.endswith("zip") -or $_.href.endswith("exe"))}
# $page.ParsedHtml.title
#$newlinks.href
Foreach ($item in $newlinks)
{
$split = $item.href.Split("/")
If ($item.href -like "*toolsdownload*")
{
Try
{
Write-host "https://www.nirsoft.net$($item.href)"
Invoke-WebRequest "https://www.nirsoft.net$($item.href)" -OutFile "$path\$($split[-1])" -ErrorAction Stop -Headers #{Referer="https://www.nirsoft.net$($item.href)"}
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
elseif ($item.href.StartsWith("http") -and $item.href.Contains(":"))
{
Try
{
Write-host "$($item.href)"
Invoke-WebRequest $item.href -OutFile "$path\$($split[-1])" -ErrorAction Stop -Headers #{Referer="$($item.href)"}
}
Catch
{
Write-Host "$($item.href)" -ForegroundColor Red
}
}
Elseif ($thing.href -like "*/dot_net_tools*")
{
Try
{
Invoke-WebRequest "https://www.nirsoft.net/dot_net_tools/$($item.href)" -OutFile "$path\$($split[-1])" -ErrorAction Stop -Headers #{Referer="https://www.nirsoft.net/dot_net_tools/$($item.href)"}
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
Else
{
Try
{
Write-Host "https://www.nirsoft.net/utils/$($item.href)"
Invoke-WebRequest "https://www.nirsoft.net/utils/$($item.href)" -OutFile "$path\$($item.href)" -ErrorAction Stop -Headers #{Referer="https://www.nirsoft.net/utils/$($item.href)"}
}
Catch
{
Write-Host $thing.href -ForegroundColor Red
}
}
If ($item.href.Contains("/"))
{
If (!(Test-Path "$path\$($split[-1])"))
{
$present = $false
}
}
Else
{
If (!(Test-Path "$path\$($item.href)"))
{
$present = $false
}
}
}
}
}
If ($present)
{
Write-Host "All of the files were downloaded!!!" -ForegroundColor Green
}
Else
{
Write-Host "Not all of the files downloaded. Something went wrong." -ForegroundColor Red
}
I'd also recommend you turn the download routine into a function that you can pass the relative URL portion so you don't have to repeat code several times.

Powershell Delete Profile script - error checking not working

I have this delete profile script that prompts for a username and deletes it from each of the computers listed. The delete profile and "user is logged in" parts are both working but the part that says “No profiles found on $Computer with Name $UserName” is not. I ran my script on two computers and it successfully deleted my profile on both. I recreated my profile (logged in) and stayed logged on to one and not the other. I run it again and it gives me the message "user is logged in". For the other computer it just deleted the profile on does not display the "no profile found" message. It just skips over it and displays nothing. I have changed the "if" to an "else" but, when I do that it displays multiple lines of "no profiles found" including the computer it previously deleted the profile on.
Here is the link where most of the script is derived from.
http://techibee.com/powershell/powershell-script-to-delete-windows-user-profiles-on-windows-7windows-2008-r2/1556. Looking through the comments, no one else seemed to have any issues with that part of it.
I do not have much knowledge in PowerShell and this has just been pieced together from other scripts I have found based on our needs. Our environment is Windows 7 and Server 2008 R2. Any assistance is greatly appreciated.
$UserName=Read-host "Please Enter Username: "
$ComputerName= #("computer1","computer2")
foreach($Computer in $ComputerName) {
Write-Verbose "Working on $Computer"
if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {
$Profiles = Get-WmiObject -Class Win32_UserProfile -Computer $Computer -ea 0
foreach ($profile in $profiles) {
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = $true
try {
$profile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
if(!$profilefound) {
Write-Host -ForegroundColor Cyan "No profiles found on $Computer with Name $UserName"
}
} else {
write-verbose "$Computer Not reachable"
}
}
PowerShell has a number of automatic variables that you should avoid re-using.
$Profile is one of these, it contains the paths to the Profile scripts applicable to the current session.
Use any other variable name (ie. $userprofile) and you'll be fine:
foreach ($userprofile in $profiles) {
$objSID = New-Object System.Security.Principal.SecurityIdentifier($userprofile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = $true
try {
$userprofile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
I was able to get it working by changing the "$profilefound=$false" and making it a global variable. Also the reason why it was displaying multiple lines of "profile not found when i changed it to an else statement is because of where it was placed. It was checking against every profile on the server. When it touched every profile on the computer it displayed "profile not found".
Here is the working script.
$UserName=Read-host "Please Enter Username: "
$ComputerName= #("computer1","computer2")
$profilefound = "false"
foreach($Computer in $ComputerName) {
Write-Verbose "Working on $Computer"
if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {
$Profiles = Get-WmiObject -Class Win32_UserProfile -Computer $Computer -ea 0
foreach($userprofile in $profiles){
$objSID = New-Object System.Security.Principal.SecurityIdentifier($userprofile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = "true"
try {
$userprofile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
}
else {
write-verbose "$Computer Not reachable"
}
if ($profilefound -eq "false") {
Write-Host -ForegroundColor Cyan "No profiles found on $Computer with Name $UserName"
}
}

Retrieve the Windows Identity of the AppPool running a WCF Service

I need to verify that the underlying server-side account running my WCF Service has correct ACL permissions to various points on the local file system. If I can get the underlying Windows Identity, I can take it from there. This folds into a larger Powershell script used after deployment.
Below is my powershell snippet, that get the ApplicationPoolSid, how do you map this to the AppPool's Windows Identity?
$mywcfsrv = Get-Item IIS:\AppPools\<MyWCFServiceName>;
Updated below to include Keith's snippet
For completeness, here's the solution:
Function Get-WebAppPoolAccount
{
param ( [Parameter(Mandatory = $true, Position = 0)]
[string]
$AppPoolName )
# Make sure WebAdmin module is loaded.
$module = (Get-Module -ListAvailable) | ForEach-Object { if ($_.Name -like 'WebAdministration') { $_ } };
if ($module -eq $null)
{
throw "WebAdministration PSSnapin module is not available. This module is required in order to interact with WCF Services.";
}
Import-Module $module;
# Get the service account.
try
{
$mywcfsrv = Get-Item (Join-Path "IIS:\AppPools" $AppPoolName);
}
catch [System.Exception]
{
throw "Unable to locate $AppPoolName in IIS. Verify it is installed and running.";
}
$accountType = $mywcfsrv.processModel.identityType;
$account = $null;
if ($accountType -eq 'LocalSystem')
{
$account = 'NT AUTHORITY\SYSTEM';
}
elseif ($accountType -eq 'LocalService')
{
$account = 'NT AUTHORITY\LOCAL SERVICE';
}
elseif ($accountType -eq 'NetworkService')
{
$account = 'NT AUTHORITY\NETWORK SERVICE';
}
elseif ($accountType -eq 'SpecificUser')
{
$account = $mywcfsrv.processModel.userName;
}
return $account;
}
Like so:
$mywcfsrv = Get-Item IIS:\AppPools\<MyWCFServiceName>
$mywcfsrv.processModel.identityType

Resources