How to remove user profiles with PowerShell - powershell

I have the script below. If I uncomment the line commented #3, I get the error
Exception calling "Delete" with "0" argument(s): ""
At Z:\Scripts\Powershell\Remove-UserProfile.ps1:48 char:21
+ $Profile.Delete()
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
regardless of whether I interrogate WMI using the format in #1 or #2. If I leave #3 commented, and uncomment #4, I get the error
Remove-WmiObject :
At Z:\Scripts\Powershell\Remove-UserProfile.ps1:49 char:21
+ Remove-WmiObject -InputObject $Profile
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Remove-WmiObject], COMException
+ FullyQualifiedErrorId : RemoveWMICOMException,Microsoft.PowerShell.Commands.RemoveWmiObject
regardless of the query string for Get-WMIObject.
Everything I can find on the web - including a couple of other SO questions - implies that either of these methods should work, but neither seems to. I have checked to see if the target profile is loaded, and it is not. Why can I not use WMI to delete user profiles? What can I do that does work, and doesn't involve downloading a utility from a third-party (which is not permitted by our "information security" team)?
Script:
function Remove-UserProfile {
<#
.SYNOPSIS
Removes user profiles from computers
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:ComputerName,
[Alias("UserName","sAMAccountName")]
[String]$Identity,
[Int]$Age,
[Switch]$DomainOnly
)
BEGIN {
$NoSystemAccounts = "SID!='S-1-5-18' AND SID!='S-1-5-19' AND SID!='S-1-5-20' " # Don't even bother with the system accounts.
if ($DomainOnly) {
$SIDQuery = "SID LIKE '$((Get-ADDomain).DomainSID)%' " # All domain account SIDs begin with the domain SID
} elseif ($Identity.Length -ne 0) {
$SIDQuery = "SID LIKE '$(Get-UserSID -AccountName $Identity)' "
}
$CutoffDate = (Get-Date).AddDays(-$Age)
$Query = "SELECT * FROM Win32_UserProfile "
}
PROCESS{
ForEach ($Computer in $ComputerName) {
Write-Verbose "Processing Computer $Computer..."
if ($SIDQuery) {
$Query += "WHERE " + $SIDQuery
$FilterStr = $SIDQuery
} else {
$Query += "WHERE " + $NoSystemAccounts
$FilterStr = $NoSystemAccounts
}
Write-Verbose "Querying WMI using '$Query' and filtering for profiles last used before $CutoffDate ..."
#1 $Profiles = Get-WMIObject -Query $Query | Where-Object { [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
#2 $Profiles = Get-WMIObject -ComputerName $Computer -Class Win32_UserProfile -Filter $FilterStr | Where-Object { [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
ForEach ($Profile in $Profiles) {
if ($PSCmdlet.ShouldProcess($Profile)) {
#3 $Profile.Delete()
#4 Remove-WmiObject -InputObject $Profile
}
}
}
}
END {}
}

Edit:
You have to run the script as Administrator.

Related

PowersShell script doesn't work remotely with Invoke-Command but works perfectly locally

I have a script to check for and download windows updates. It isn't working remotely and I would like to know why. Here is the script:
$UpdateSession = New-Object -Com Microsoft.Update.Session
$updatesToDownload = New-Object -Com Microsoft.Update.UpdateColl
$updatesToInstall = New-Object -Com Microsoft.Update.UpdateColl
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'")
$Downloader = $UpdateSession.CreateUpdateDownloader()
$Installer = $UpdateSession.CreateUpdateInstaller()
# Check for updates ---------------------------------------------------------------------
if ($SearchResult.Updates.Count -gt 0) {
Write-Host("All updates found: " + $SearchResult.Updates.Count)
For ($X = 0; $X -lt $SearchResult.Updates.Count; $X++) {
$Update = $SearchResult.Updates.Item($X)
if ($Update.KBArticleIDs -eq '2267602') {
Write-Host("Updates with Ids 2267602 found: " + $Update.Title)
$updatesToDownload.Add($Update)
Write-Host("Update " + $Update.Title + " added to download list")
}
}
}
else {
# Write-Host(0) # No Security Intelligence Updates
Write-Host("No update found")
Exit
}
# Download updates ----------------------------------------------------------------------
if ($updatesToDownload.Count -gt 0) {
Write-Host("Start download process")
$Downloader.Updates = $updatesToDownload
$DownloadResult = $Downloader.Download()
Write-Host("Download ResultCode: " + $DownloadResult.ResultCode)
if ($DownloadResult.ResultCode -eq 2) {
For ($X = 0; $X -lt $updatesToDownload.Count; $X++) {
Write-Host("Adding updates to install list")
$Update = $updatesToDownload.Item($X)
if ($update.IsDownloaded -eq 'True') {
$updatesToInstall.Add($Update)
Write-Host("Update " + $Update.Title + " added to install list")
}
}
}
}
else {
# Write-Host(0) # No Security Intelligence Updates
Write-Host("No update to download found")
Exit
}
The script works perfectly when I run it on a computer using administrator account.
But when I try to run it remotely using below short script and the same administrator credentials it starts and do "Check for updates" bloc and stops with error.
$password = ConvertTo-SecureString "some_password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("computer_name\administrator", $password)
$sess = New-PSSession -ComputerName "computer_name" -Credential $cred
Invoke-Command -Session $sess -FilePath "D:\Test.ps1"
Where "D:\Test.ps1" is path to a file with script mentioned above.
Error message:
The property 'Updates' cannot be found on this object. Verify that the property exists and can be set.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
+ PSComputerName : computer_name
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : computer_name
I have checked remote credentials using other script (below) and the result is True yet updates script doesn't work.
[bool] (net session 2>$null)
[Security.Principal.WindowsPrincipal]::new(
[Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
What did I do wrong? Why doesn't the script work remotely?

Getting error when connecting two Azure Analysis Services from child scripts

I am working on PowerShell scripts. I have two scripts in those scripts I am connecting with two Azure Analysis Servers one by one. And these scripts are calling by the main script. I am getting errors
"Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an object."
My Scripts code is below
Child1.ps1
param(
[String]
$envName1,
[String]
$toBeDisconnect1
)
$loadInfo1 = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
$server1 = New-Object Microsoft.AnalysisServices.Server
if($toBeDisconnect1 -eq "No")
{
$server1.Connect($envName1)
return $server1
}
elseif($toBeDisconnect1 -eq "Yes")
{
$server1.Disconnect()
Write-Host $server1 " has been disconnected."
}
Child2.ps1
param(
[String]
$envName1,
[String]
$toBeDisconnect1
)
$loadInfo1 = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
$server1 = New-Object Microsoft.AnalysisServices.Server
if($toBeDisconnect1 -eq "No")
{
$server1.Connect($envName1)
return $server1
}
elseif($toBeDisconnect1 -eq "Yes")
{
$server1.Disconnect()
Write-Host $server1 " has been disconnected."
}
MainParent.ps1
param(
$filePath = "C:\Users\user1\Desktop\DBList.txt"
)
$command = "C:\Users\User1\Desktop\test1.ps1 –envName1
asazure://aspaaseastus2.asazure.windows.net/mydevaas -toBeDisconnect1 No"
$Obj1 = Invoke-Expression $command
Start-Sleep -Seconds 15
$command1 = "C:\Users\User1\Desktop\test2.ps1 –envName2
asazure://aspaaseastus2.asazure.windows.net/myuataas -toBeDisconnect2 No"
$Obj2 = Invoke-Expression $command1
Error is below in MainParent.ps1
Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an
object."
At C:\Users\User1\Desktop\test1.ps1:13 char:5
+ $server1.Connect($envName1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NullReferenceException
Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an
object."
At C:\Users\User1\Desktop\test2.ps1:13 char:5
+ $server2.Connect($envName2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NullReferenceException
My AzureAnalysis Services dll version is 13.0.4495.10. I am sharing this info, may be it can be a issue.
It seems Invoke-Expression is not passing the param envName1. I would rather call the child script normally like below:
$Obj1 = &"C:\Users\User1\Desktop\test1.ps1" -envName1 "asazure://aspaaseastus2.asazure.windows.net/mydevaas" -toBeDisconnect1 No | select -Last 1
$Obj2 = &"C:\Users\User1\Desktop\test2.ps1" -envName2 "asazure://aspaaseastus2.asazure.windows.net/myuataas" -toBeDisconnect2 No | select -Last 1
$command = "C:\Users\User1\Desktop\test1.ps1 –envName1 asazure://aspaaseastus2.asazure.windows.net/mydevaas -toBeDisconnect1 No"
Look closely at the hyphen before envName1. It's actually – (en-dash) instead of - (hyphen). That's the reason envName1 is not being passed. The second param toBeDisconnect1 has it correct.
How to know the difference? - (hyphen) is shorter than – (en-dash) :)

Dynamically created parameter arguments for PowerShell's Get-ChildItem

Long story short, I'm trying to dynamically use a parameter -Directory or -File in PowerShell's Get-ChildItem. Guess what? I'm unable to.
Here's the deal (note: pseudo-code):
Param(
[string]$filter = $(throw "Error: name"),
[string]$type = $(throw "error: file or directory")
)
if( $type -eq "file" ) {
$mode = '-File'
}
elseif( $type -eq "directory" ) {
$mode = '-Directory'
}
Function Find_Plugin_folder {
Write-Host "look for: '$($filter)'"
Invoke-Command -ComputerName (Get-Content servers.txt ) -ScriptBlock {
(Get-ChildItem -Path "z:\www" -Depth 5 $Using:mode -Filter $Using:filter -Recurse ) | %{$_.FullName}
} -ThrottleLimit 80
}
Find_Plugin_folder
$Using:mode is where it throws an error, either:
PS C:\Users\janreilink> v:\test.ps1 vevida-optimizer file
look for: 'vevida-optimizer'
A positional parameter cannot be found that accepts argument '-File'.
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
+ PSComputerName : webserver-01.example.net
Or
PS C:\Users\janreilink> v:\test.ps1 vevida-optimizer directory
look for: 'vevida-optimizer'
A positional parameter cannot be found that accepts argument '-Directory'.
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
+ PSComputerName : webserver-01.example.net
I've been reading about Dynamic Parameter sets all afternoon, but can't wrap my head around it yet. Any points are much (much, much) appreciated.
You'll want to use splatting for this instead. Start by creating a hashtable with some or all of the parameters you want to pass:
$dynamicArgs = #{}
if( $type -eq "file" ) {
$dynamicArgs['File'] = $true
}
elseif( $type -eq "directory" ) {
$dynamicArgs['Directory'] = $true
}
Then, inside Invoke-Command, prefix the variable name with # to indicate that you want to "splat" the arguments:
Get-ChildItem -Path "z:\www" -Depth 5 #Using:dynamicArgs -Filter $Using:filter -Recurse
If the splatting table contains the key File with a value of $true, it's the equivalent of adding -File:$true on the command line, and vice versa for the Directory argument

PS Get-WinEvent throw 'The Handle is invalid'

I have a list of hostnames from which I'd like to extract all AppLocker related eventlogs, especially the ones with level warning and/or error.
I crafted this script:
$ComputersToCheck = Get-Content 'X:\ListWithTheNames.txt'
foreach($OneHost in $ComputersToCheck)
{
try
{
$EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ComputerName $OneHost -Credential $CredentialFromUser
foreach ($SingelEvent in $EventCollection)
{
if($SingelEvent.LevelDisplayName -ne "Information")
{
$pathtosaveto = 'SomeFileName.txt'
$ResultString += $SingelEvent | Select Message,MachineName,UserId | Export-Csv -Path $pathtosaveto -Append
}
}
}
catch
{
//handling exceptions
}
}
This works for a while, but after a certain ammount of time I got an error:
Get-WinEvent : The remote procedure call failed
At X:\FileName.ps1:22 char:28
+ $EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EX ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The remote procedure call failed,Microsoft.PowerShell.Commands.GetWinEventCommand
And right after the script start giving errors like this:
Get-WinEvent : The handle is invalid
At X:\FileName.ps1:22 char:28
+ $EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EX ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The handle is invalid,Microsoft.PowerShell.Commands.GetWinEventCommand
My first thought was that it is related to the host the script try to reach, but the next in the list is the same type (Os, even the same model) as the previous.
I ran the script 3 times, and every time the output size was different (probably because not the same hosts were online with the same amount of logs).
The script should run against more than 700 hosts, to which a special account is needed which I prompt by the Get-Credential, store in a variable and pass it the the Get-WinEvent as a parameter.
To be honest I stuck with this issue, not really sure what cause this and why.
If anyone has an idea please share with me :)
Give this a try to attempt catching references to failed hosts and empty objects. You could write the exception received but I didn't include that in this to make the failedhosts file simple to read. Hope I got it right as I winged it and don't have a true case to test against.
$ComputersToCheck = Get-Content 'X:\ListWithTheNames.txt'
foreach($OneHost in $ComputersToCheck) {
try {
$EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ComputerName $OneHost -Credential $CredentialFromUser -ErrorAction Stop
if($EventCollection) {
foreach ($SingelEvent in $EventCollection) {
if($SingelEvent.LevelDisplayName -ne "Information") {
$pathtosaveto = 'SomeFileName.txt'
$ResultString += $SingelEvent | Select Message,MachineName,UserId | Export-Csv -Path $pathtosaveto -Append
}
}
} else {
Out-File -InputObject $($OneHost + " Empty Event Collection") -FilePath "C:\FailedHosts.txt" -Append -Encoding ascii
}
}
catch {
Out-File -InputObject $($OneHost + " Failed Connection") -FilePath "C:\FailedHosts.txt" -Append -Encoding ascii
}
}

PowerShell script inside SQL Agent job error

I'm trying to run the code below via a script in a SQL agent job on a drive which is failing. When I logon as the service account user and run it in an ISE shell it works fine which leads me to believe it's not access related.
I tried running it as a PowerShell job step but it wouldn't work so decided to run it as a cmdexec job type and call it like this:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -file "F:\Powershell\ScriptOutSSRSEncryptionKeys.ps1"
Script
$ComputerName = "servername"
$KeyFolder = "\\servername\sharename\SSRSKEYS\"
$KeyPassword = "Password1"
$TimeStamp = Get-Date -Format "-yyyyMMdd-HHmmss"
Get-WmiObject -Namespace "Root\Microsoft\SqlServer\ReportServer" -Class "__Namespace" -ComputerName $ComputerName |
Select-Object -ExpandProperty Name |
% {
$NameSpaceRS = $_
$InstanceName = $NameSpaceRS.SubString(3)
$KeyFileName = Join-Path -Path $KeyFolder -ChildPath ($InstanceName + $Timestamp + ".snk")
$SQLVersion = (Get-WmiObject -Namespace "Root\Microsoft\SqlServer\ReportServer\$($NameSpaceRS)" -Class "__Namespace" - ComputerName $ComputerName).Name
$SSRSClass = Get-WmiObject -Namespace "Root\Microsoft\SqlServer\ReportServer\$($NameSpaceRS)\$($SQLVersion)\Admin" - Query "SELECT * FROM MSReportServer_ConfigurationSetting WHERE InstanceName='$($InstanceName)'" -ComputerName $ComputerName
$Key = $SSRSClass.BackupEncryptionKey($KeyPassword)
If ($Key.HRESULT -ne 0) {
$Key.ExtendedErrors -join "`r`n" | Write-Error
} Else {
$Stream = [System.IO.File]::Create($KeyFileName, $Key.KeyFile.Length)
$Stream.Write($Key.KeyFile, 0, $Key.KeyFile.Length)
$Stream.Close()
}
}
Error
Executed as user: domain\svc_account. Exception calling "Create" with "2"
argument(s): "Access to the path '\\servername\sharename\SSRSKEYS\MSSQLSERVER-20150824-125254.snk' is denied." At
F:\Powershell\ScriptOutSSRSEncryptionKeys.ps1:24 char:13 + $Stream = [System.IO.File]::Create($KeyFileName, $Key.KeyFile.Length ... +
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +
CategoryInfo : NotSpecified: (:) [], MethodInvocationException +
FullyQualifiedErrorId : UnauthorizedAccessException You cannot call a
method on a null-valued expression. At
F:\Powershell\ScriptOutSSRSEncryptionKeys.ps1:25 char:13 +
$Stream.Write($Key.KeyFile, 0, $Key.KeyFile.Length) +
Try passing the script path to the PowerShell.exe as a parameter using the & operator, e.g.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe "& 'F:\Powershell\ScriptOutSSRSEncryptionKeys.ps1'"
The call operator (&) allows you to execute a command, script or function.
Syntax
& "[path] command" [arguments]