How to get win32_service recovery tab properties - windows

Regarding service's recovery tab properties that can be seen here:
Is there an API to get the following property values:
First failure for example value: "Take no action"
Second failure
Subsequent failures
Reset fail count
I prefer a way to do so in PowerShell but would like to know about other options as well.

I am not familiar with PowerShell, but there is a Win32 API available: QueryServiceConfig2(). Set the dwInfoLevel parameter to SERVICE_CONFIG_FAILURE_ACTIONS, and pass a pointer to a buffer in the lpBuffer parameter that is large enough to receive a SERVICE_FAILURE_ACTIONS struct.

One needs to modify the services reg key, under
HKLM\System\CurrentControlSet\services\<service name>\
Adding a value of type binary with the name FailureActions. I don't know how it's structured you'd have to play around with that, but as it relates to powershell it would simply be grabbing to real name of the service (maybe using get-service if all you have is the display name), and navigating to that regkey and creating a new value, for example:
PS C:\Users\*\Desktop> $ByteArray = 0,0,0,144,10,23,253,33
PS C:\Users\*\Desktop> Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\services\AdobeARMservice -Name FailureActions -Type Binary -Value $ByteArray -Force
PS C:\Users\*\Desktop> Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\services\AdobeARMservice -Name FailureActions
FailureActions : {0, 0, 0, 144...}
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\AdobeARMservice
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services
PSChildName : AdobeARMservice
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
Adding a byte[ ], but like I mentioned you'd have to either reverse engineer the meaning of the array, or just copy an existing one or something similar.

You can control it for instance with cs.exe
Get-Service -DisplayName YourService | % { sc.exe failure $_.Name actions= /0 reset= 0 }

Related

Windows RegistryFilter Kernel Driver: UnauthorizedAccessException when creating registry key path with multiple missing keys via the driver

I am trying to create a registry filter which intercepts create operation in the pre-notification. In the driver, i am creating the registry in the new path for example, if the original create was for: HKLM:\Software\xyz i will create the key in my driver at: HKLM\Software\MyHive\xyz and return STATUS_CALLBACK_BYPASS.
Once deployed, when i try to create 1 new registry key using Powershell it works but when multiple missing keys are in the path, it fails.
Example:
Success:
New-Item -Path HKLM:\Software\test1 -Force -Verbose
VERBOSE: Performing the operation "New Item" on target "Item: HKEY_LOCAL_MACHINE\Software\test1".
Hive: HKEY_LOCAL_MACHINE\Software
Name Property
test1
Failure:
New-Item -Path HKLM:\Software\test2\a\b\c -Force -Verbose
VERBOSE: Performing the operation "New Item" on target "Item: HKEY_LOCAL_MACHINE\Software\test2\a\b\c".
New-Item : Access to the registry key 'HKEY_LOCAL_MACHINE\Software\test2\a\b\c' is denied.
At line:1 char:1
New-Item -Path HKLM:\Software\test2\a\b\c -Force -Verbose
+ CategoryInfo : PermissionDenied: (HKEY_LOCAL_MACHINE\Software\test2\a\b\c:String) [New-Item], Unauthori
zedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.NewItemCommand
I see that only test2 key is created under MyHive and none of the sub-keys under test2.
####More details
Exact same command works without my filter.
When I look at proc-mon which is running at altitude above my driver, i see the following calls:
Create HKLM:\Software\test2\a\b\c -> NAME_NOT_FOUND
Create HKLM:\Software\test2 -> SUCCESS with actual location being: HKLM:\Software\MyHive\test2
RegQuerySecurity -> HKLM:\Software\MyHive\test2
RegClose
As compare to when my filter is not there: i see:
Create HKLM:\Software\test2\a\b\c -> NAME_NOT_FOUND
Create HKLM:\Software\test2 -> SUCCESS
RegOpen-> HKLM:\Software\MyHive\test2
Create: HKLM:\Software\test2\a
and so on.
My driver code is:
CreateKeyInfo -> This is the pointer to REG_CREATE_KEY_INFORMATION_V1
// orig key does not exist, create a new one and send caller handle of that.
InitializeObjectAttributes(
&objectAttributes,
&newAbsoluteKeyPath, // Path with MyHive
CreateKeyInfo->Attributes | OBJ_KERNEL_HANDLE,
NULL,
CreateKeyInfo->SecurityDescriptor
);
// TODO: Handle transactions.
if (CreateKeyInfo->Transaction) {
LogWarn("Transaction object is not null.");
}
status = ZwCreateKey(
&newKeyHandle,
KEY_ALL_ACCESS,
&objectAttributes,
0,
CreateKeyInfo->Class,
CreateKeyInfo->Options,
CreateKeyInfo->Disposition
);
if (!NT_SUCCESS(status)) {
goto HandleRegistryPreCreateCleanup;
}
status = ObReferenceObjectByHandle(
newKeyHandle,
CreateKeyInfo->DesiredAccess,
CreateKeyInfo->ObjectType,
KernelMode,
&resultObject,
NULL);
if (!NT_SUCCESS(status)) {
goto HandleRegistryPreCreateCleanup;
}
CreateKeyInfo->GrantedAccess = CreateKeyInfo->DesiredAccess;
*CreateKeyInfo->ResultObject = resultObject;
status = STATUS_CALLBACK_BYPASS;
Could you please help me understand what is wrong here?

What is happening with these PSDriveInfo objects?

I have a few scripts that create multiple instances of PSDrive to remote instances. I want to make certain that each instance of PSDrive created is cleaned up.
I have a Powershell module like the following. This is a simplified version of what I actually run:
function Connect-PSDrive {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
$Root,
[String]
$Name = [Guid]::NewGuid().ToString(),
[ValidateSet("Registry","Alias","Environment","FileSystem","Function","Variable","Certificate","WSMan")]
[String]
$PSProvider = "FileSystem",
[Switch]
$Persist = $false,
[System.Management.Automation.PSCredential]
$Credential
)
$parameters = #{
Root = $Root;
Name = $Name;
PSProvider = $PSProvider;
Persist = $Persist;
}
$drive = $script:drives | Where-Object {
($_.Name -eq $Name) -or ($_.Root -eq $Root)
}
if (!$drive) {
if ($Credential) {
$parameters.Add("Credential", $Credential)
}
$script:drives += #(New-PSDrive #parameters)
if (Get-PSDrive | Where-Object { $_.Name -eq $Name }) {
Write-Host "The drive '$Name' was created successfully."
}
}
}
function Disconnect-PSDrives {
[CmdletBinding()]
param ()
$script:drives | Remove-PSDrive -Force
}
Each time I invoke the function Connect-PSDrive, I can see that a new drive is successfully created and a reference is added to $script:drives. At the end of the calling script, I have a finally block that invokes Disconnect-PSDrives and this fails with the following exception.
Remove-PSDrive : Cannot find drive. A drive with the name 'mydrive' does not exist.
At C:\git\ops\release-scripts\PSModules\PSDriveWrapper\PSDriveWrapper.psm1:132 char:22
+ $script:drives | Remove-PSDrive -Force
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (mydrive:String) [Remove-PSDrive], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.RemovePSDriveCommand
I want to know why references to the PSDrive objects I created are available in $script:drives, and yet Remove-PSDrive fails to locate the objects.
I also want to know how I can manage these PSDrive instances without needing to return each instance to the calling script such that Disconnect-PSDrives works.
A few extra notes:
I'm creating these drives with the Persist flag as false.
Running these multiple times errors with too many multiple connections being made to a machine. This is why I think that connections are not being cleaned up. If my assumption is wrong, please kindly explain why connections are cleaned up.
I am a little surprised that it cannot remove from the object reference; but I assume that your issue is with scope. PSDrives are local scope by default so when your function exits, they are no longer visible. Use the -Scope parameter for New-PSDrive and you will likely be successful. (As a side note: during Disconnect-PSDrives you will likely want to clear the list in case of multiple calls.)
That being said, you should never need to clean up the PSDrives like you are doing. Likely the reason you are experiencing too many connections is, once again, a scoping issue (that is, they still exist but you no longer see them). Try running it multiple times where you close PowerShell and start a new instance each time--you will no longer see too many connections. Why? Because PowerShell cleans up all non-persistent drives at the end of your session. You do not need to clean up the drives between sessions/instances; and within an session/instance (assuming you have proper scoping) you can re-use the drives so there is no need to create duplicates; ergo, you should never really need this functionality. That being said, I might assume you have some niche use case for this?

How to configure 32-bit IIS websites or applications using PowerShell cmdlets

I am trying to change the location of the ASP.NET temporary files so that I can clean them up during the release of a new version.
Because it is very hard to find the location of the ASP.NET temporary files for a specific website, application of virtual directory under the C:\Windows\Microsoft.NET\Framework C:\Windows\Microsoft.NET\Framework64 locations I decided it would be easier just to move the files to a specific location on the disk which can then be cleaned.
You can do this by modifying the tempDirectory attribute in the system.web/compilation configuration section.
We have our server build and release processes automated therefore this looked straightforward to add the code to the configuration and release scripts.
However during testing I found that the location of 32-bit applications does not change.
The code I am using is:
Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT' -location 'MyWebSite' -filter 'system.web/compilation' -name 'tempDirectory' -value 'E:\Temporary ASP.NET Files\MyWebSite' -Clr v2.0
This code works without errors and writes an entry into the root web.config file at: C:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG\web.config.
e.g.
<location path="MyWebSite">
<system.web>
<compilation tempDirectory="E:\Temporary ASP.NET Files\MyWebSite" />
</system.web>
</location>
Note that without the -Clr v2.0 parameter, the value would be written to the CLR 4.0 configuration file at C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config.
You can see the entry in IIS Configuration Editor as well:
The problem is the application pool is set to "Enable 32-Bit Applications" and therefore this property is ignored.
If I manually move the location element shown above from C:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG\web.config to C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config
The change works and indeed the ASP.NET temporary files move to this location specified when the compilation of the website occurs.
The question is not about ASP.NET temporary files, it is a more general question on how are you meant to configure 32-bit applications in PowerShell or indeed in IIS? There seems to be no way to do it which I find incredible. There are plenty of 32-bit applications around which still need to be configured.
Also please note that I have chosen to use the root web.config to store this value for good reasons:
ApplicationHost.config cannot store system.web configuration
The location of the temporary files is not something that is known at design time and is determined by the hosting server configuration. Therefore this setting cannot be specified in the web.config file for the application itself.
there are two workarounds.
Use 32 bit appcmd.exe. Here is the example.
%windir%\syswow64\cmd.exe
%windir%\syswow64\inetsrv\appcmd.exe set config -section:appSettings /+"[key='test',value='test']" /commit:webroot /clr:4.0
Use MWA directly on powershell. Here is the example.
$iis = new-object Microsoft.Web.Administration.ServerManager
$wcm = New-Object -TypeName Microsoft.Web.Administration.WebConfigurationMap -ArgumentList "C:\Windows\Microsoft.NET\Framework\v4.0.30319\CONFIG\machine.config","C:\Windows\Microsoft.NET\Framework\v4.0.30319\CONFIG\web.config"
$iis.GetWebConfiguration($wcm, $null).GetSection("appSettings").SetAttributeValue("file", "test3")
$iis.CommitChanges()
After experimentation, using some information from the answer provided here and an offline conversation I can say conclusively that it is not possible to edit 32-bit root web.config files using the Microsoft WebAdministration PowerShell cmdlets.
It seems that the cmdlets are hard coded to only look at the 64-bit versions of the configuration files. IIS Manager behaves the same way. Why this is the case is inexplicable to me.
I also found a lot of problems using some of the cmdlets for editing 64 bit Clr 2.0 websites and applications. The Clr parameter is not present on all the cmdlets and even in the ones where it is it does not seem to always work.
Therefore I decided to abandon the WebAdministration cmdlets and use the 'Microsoft.Web.Administration.dll' assembly and the Microsoft.Web.Administration.ServerManager object directly.
The following are some of the functions I wrote which might be helpful:
function Get-MWAConfigObjects
{
<#
.SYNOPSIS
Returns object to manage IIS configuration in root web.config
.DESCRIPTION
The objects returned allow viewing or editing or configuration. Parameters open the appropriate version, either 32 or 64 bit for the appropriate version of the ManagedRunTime. https://msdn.microsoft.com/en-us/library/microsoft.web.administration.servermanager(v=vs.90).aspx
Ensure that you call CommitChanges to save any changes made.
.EXAMPLE
$MWA = Get-MWAConfigObjects -ManagedRunTimeVersion v2.0 -Architecture 32 $MWA.Configuration.GetSection('system.web/compilation','MyWebSite/MyApplication').SetAttributeValue('tempDirectory', 'C:\NewPath')
$MWA.ServerManager.CommitChanges()
#>
[cmdletbinding(positionalbinding = $false)]
param(
[Parameter(Mandatory = $True)][string][ValidateSet('v2.0','v4.0')] $ManagedRunTimeVersion,
[Parameter(Mandatory = $True)][string][ValidateSet(32,64)] $Architecture
)
$assemblyPath = $(Join-Path -Path $([System.Environment]::GetFolderPath('System')) -ChildPath $(Join-Path -Path 'inetsrv' -ChildPath 'Microsoft.Web.Administration.dll'))
If (Test-Path -Path $assemblyPath -PathType Leaf)
{
$null = [System.Reflection.Assembly]::LoadFrom($assemblyPath)
$iis = New-Object -TypeName Microsoft.Web.Administration.ServerManager
$wcm = New-Object -TypeName Microsoft.Web.Administration.WebConfigurationMap -ArgumentList $(Get-ConfigFilePath -Type machine -ManagedRunTimeVersion $ManagedRunTimeVersion -Architecture $Architecture), $(Get-ConfigFilePath -Type web -ManagedRunTimeVersion $ManagedRunTimeVersion -Architecture $Architecture)
$configuration = $iis.GetWebConfiguration($wcm, $null)
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name ServerManager -Value $iis
$object | Add-Member -MemberType NoteProperty -Name Configuration -Value $configuration
Write-Output -InputObject $object
}
else
{
Throw "Cannot validate existence of required assembly 'Microsoft.Web.Administration.dll' at ""$assemblyPath"""
}
}
function Get-ConfigFilePath
{
[CmdletBinding(PositionalBinding = $false)]
param
(
[Parameter(Mandatory = $True)][string][ValidateSet('web','machine')] $Type,
[Parameter(Mandatory = $True)][string][ValidateSet('v2.0','v4.0')] $ManagedRunTimeVersion,
[Parameter(Mandatory = $True)][string][ValidateSet(32,64)] $Architecture
)
$ErrorActionPreference = 'stop'
switch ($ManagedRunTimeVersion)
{
'v2.0'
{
switch ($Architecture)
{
32
{
$path = $(Join-Path -Path $([System.Environment]::GetFolderPath('Windows')) -ChildPath "Microsoft.NET\Framework\v2.0.50727\CONFIG\$Type.config")
break
}
64
{
$path = $(Join-Path -Path $([System.Environment]::GetFolderPath('Windows')) -ChildPath "Microsoft.NET\Framework64\v2.0.50727\CONFIG\$Type.config")
break
}
}
break;
}
'v4.0'
{
switch ($Architecture)
{
32
{
$path = $(Join-Path -Path $([System.Environment]::GetFolderPath('Windows')) -ChildPath "Microsoft.NET\Framework\v4.0.30319\CONFIG\$Type.config")
break
}
64
{
$path = $(Join-Path -Path $([System.Environment]::GetFolderPath('Windows')) -ChildPath "Microsoft.NET\Framework64\v4.0.30319\CONFIG\$Type.config")
break
}
}
break;
}
}
If (Test-Path -Path $path -PathType Leaf)
{
Write-Output -InputObject $path
}
else
{
Throw "Cannot validate configuration file at path ""$path"""
}
}

Find accounts innactive for X days in specific OUs

I am trying to get a PowerShell script running that will display a list of all users who have been inactive (or not logged in) in x days. That part was easy enough to find and modify a script for, but I am having trouble setting it so I can specify only certain OUs and sub OUs within the domain. This is what I have so far, though I think I might have to use another method to accomplish this:
#Import Ad Module
Import-Module ActiveDirectory
#SearchBase
$searchB = (Get-Content -Path C:\scripts\ous.txt)
#Time accounts have been inactive
$tSpan = "145"
Search-ADAccount -SearchBase $searchB -AccountInactive -UserOnly -Timepsan $tSpan |
Where {($_.DistinguishedName -notlike "specific sub-ou I don't want to check")} |
FT name,ObjectClass -A
The text file is in the format:
OU=first ou,OU=Parent OU,DC= thisDC,DC=dc,DC=DC
OU=third ou,OU=Parent OU,DC= this DC,DC=dc,DC=DC
OU=fourthou,OU=Parent OU,DC= thisDC,DC=dc,DC=DC
When I run this I get an error
Search-ADAccount : Directory object not found
Looks like you have a type mismatch in your -SearchBase parameter.
See Get-Help Search-ADAccount
Note that the value type for -SearchBase is string. You have three OUs in your text file, so Get-Content on that file is going to produce a string array (string[]).
Since the -SearchBase parameter will only accept a single value, you'll need to foreach through the OU list, giving on one OU at a time:
foreach ($OU in $SearchB)
{
search-adaccount -searchbase $OU -accountinactive -useronly -timepsan $tSpan.....
}

Powershell - Add (default display) object property values from pipe to string

Update 2: I ended up figuring this out while writing it. I figured I'd post it for anyone else muddling through. See sub-heading 'Resolution - Get only default properties', or the answer. Please feel free to respond with alternate (especially better!) methods or comments.
Update 1: I started out without a way to do this, I've since found a way for ALL properties. I've left the build-up for anyone else confused like I was, but my current problem is that I want JUST the default display properties - see sub-heading 'Get all Properties'
Let's say I have a collection of objects in powershell - the specific example I'm working with is a collection of events acquired using the get-winevent cmdlet.
Does anyone know an elegant way to get the values of all the (default) properties of each object, from the pipeline, and add them to the end of a string? Especially a way that doesn't involve needing to know which properties you want.
For example, using variable $events containing some event log entries, if I simply call $events powershell will make some assumptions about the properties I want and format them as a list:
PS C:\> $events
TimeCreated ProviderName Id Message
----------- ------------ -- -------
11/09/2014 3:59:... Microsoft-Window... 4634 An account was l...
11/09/2014 3:58:... Microsoft-Window... 4634 An account was l...
However, if I try to precede the returned entries with a string, I get the property names rather than values:
PS C:\> $events | %{"NEW RECORD" + $_}
NEW RECORDSystem.Diagnostics.Eventing.Reader.EventLogRecord
NEW RECORDSystem.Diagnostics.Eventing.Reader.EventLogRecord
PS C:\> $events | %{"NEW RECORD" + $_.properties}
NEW RECORDSystem.Diagnostics.Eventing.Reader.EventProperty System.Diagnostics.E
venting.Reader.EventProperty System.Diagnostics.Eventing.Reader.EventProperty S
ystem.Diagnostics.Eventing.Reader.EventProperty System.Diagnostics.Eventing.Rea
der.EventProperty
The easiest work around I could think of involved using (and therefore knowing) the property values, and also losing the notation that format-table or format-list would provide:
PS C:\> $events | %{"NEW RECORD - TimeCreated: " + $_.TimeCreated + "; ProviderName: "`
+ $_.ProviderName + "; ID: " + $_.ID + "; Message: " + $_.message}
NEW RECORD - TimeCreated: 09/11/2014 15:58:08; ProviderName: Microsoft-Windows-
Security-Auditing; ID: 4672; Message: Special privileges assigned to new logon.
Subject:
Security ID: S-*-*-**-*********-**********-**********-*****
Account Name: **********
Account Domain: **********
Logon ID: 0x**********
Privileges: SeSecurityPrivilege
Get all Properties
So I've discovered I CAN get ALL the properties, and their names, like this:
PS C:\> $events | %{"NEW RECORD" + ($_.psobject.properties | %{$_.name ; ":" ; $_.value})}
NEW RECORDMessage : Special privileges assigned to new logon.
Subject:
Security ID: S-*-*-**-*********-**********-**********-*****
Account Name: **********
Account Domain: **********
Logon ID: 0x**********
Privileges: SeSecurityPrivilege Id : 4672 Version : 0 Qualifiers :
Level : 0 <and so on>
However, I'm now pulling a bunch of stuff the consumers of my data won't need, since I only need the default properties and their names, plus a self-defined delimiter.
Is anyone aware of a notation that will return all values of all default display properties without said properties needing to be spelled out? Either a generic container for values (eg. $_.properties.value, though I tried that and it didn't work), or something like expand-property only without needing to specify a particular property name?
Resolution - Get only default properties
So it turns out I was overthinking this. FOREACH (%{}) can obviously preserve data from the pipeline across statements, so if I use two statements I can achieve the desired effect:
PS C:\> $events | format-list | %{"NEW RECORD" ; $_}
NEW RECORD
Message : An account was successfully logged on.
<and etc>
I answered this while writing it, the details are above. In order to collect all properties from an object and their values, and include both as part of a string:
PS C:\> $events | %{"NEW RECORD" + ($_.psobject.properties | %{$_.name ; ":IMASTRING:" ; $_.value})}
The above method owes a lot to the answer by Shay Levy to this question.
To include only the default properties and their values, preceded by a string:
PS C:\> $events | format-list | %{"NEW RECORD" ; $_}
To include all properties and their values, preceded by a string but retain the default formatting:
PS C:\> $events | select-object * | format-list | %{"NEW RECORD"; $_}
I think you've done things the easy and maybe best way for your situation. There actually is a way to know the names of the default properties
PS Scripts:\> $x = gwmi -Class win32_operatingsystem
PS Scripts:\> $x.psstandardmembers
PSStandardMembers {DefaultDisplayPropertySet}
PS Scripts:\> $x.psstandardmembers.DefaultDisplayPropertySet
ReferencedPropertyNames : {SystemDirectory, Organization, BuildNumber, RegisteredUser...}
MemberType : PropertySet
Value : DefaultDisplayPropertySet {SystemDirectory, Organization, BuildNumber, RegisteredUser,
SerialNumber, Version}
TypeNameOfValue : System.Management.Automation.PSPropertySet
Name : DefaultDisplayPropertySet
IsInstance : False
PS Scripts:\> $x.psstandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames
SystemDirectory
Organization
BuildNumber
RegisteredUser
SerialNumber
Version
This is the post that I found this information on PSStandard Members.

Resources