mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Remove FusionAssemblyIdentity and GlobalAssemblyCache as they are not used (#18334)
This commit is contained in:
@@ -2804,12 +2804,6 @@ namespace System.Management.Automation.Language
|
||||
assemblyFileName = Path.GetDirectoryName(scriptFileName) + "\\" + assemblyFileName;
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
if (!File.Exists(assemblyFileName))
|
||||
{
|
||||
Microsoft.CodeAnalysis.GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName);
|
||||
}
|
||||
#endif
|
||||
if (File.Exists(assemblyFileName))
|
||||
{
|
||||
assembly = Assembly.LoadFrom(assemblyFileName);
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Code in this file was copied from https://github.com/dotnet/roslyn
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#if !CORECLR
|
||||
namespace Microsoft.CodeAnalysis
|
||||
{
|
||||
internal sealed class FusionAssemblyIdentity
|
||||
{
|
||||
[Flags]
|
||||
internal enum ASM_DISPLAYF
|
||||
{
|
||||
VERSION = 0x01,
|
||||
CULTURE = 0x02,
|
||||
PUBLIC_KEY_TOKEN = 0x04,
|
||||
PUBLIC_KEY = 0x08,
|
||||
CUSTOM = 0x10,
|
||||
PROCESSORARCHITECTURE = 0x20,
|
||||
LANGUAGEID = 0x40,
|
||||
RETARGET = 0x80,
|
||||
CONFIG_MASK = 0x100,
|
||||
MVID = 0x200,
|
||||
CONTENT_TYPE = 0x400,
|
||||
FULL = VERSION | CULTURE | PUBLIC_KEY_TOKEN | RETARGET | PROCESSORARCHITECTURE | CONTENT_TYPE
|
||||
}
|
||||
|
||||
internal enum PropertyId
|
||||
{
|
||||
PUBLIC_KEY = 0, // 0
|
||||
PUBLIC_KEY_TOKEN, // 1
|
||||
HASH_VALUE, // 2
|
||||
NAME, // 3
|
||||
MAJOR_VERSION, // 4
|
||||
MINOR_VERSION, // 5
|
||||
BUILD_NUMBER, // 6
|
||||
REVISION_NUMBER, // 7
|
||||
CULTURE, // 8
|
||||
PROCESSOR_ID_ARRAY, // 9
|
||||
OSINFO_ARRAY, // 10
|
||||
HASH_ALGID, // 11
|
||||
ALIAS, // 12
|
||||
CODEBASE_URL, // 13
|
||||
CODEBASE_LASTMOD, // 14
|
||||
NULL_PUBLIC_KEY, // 15
|
||||
NULL_PUBLIC_KEY_TOKEN, // 16
|
||||
CUSTOM, // 17
|
||||
NULL_CUSTOM, // 18
|
||||
MVID, // 19
|
||||
FILE_MAJOR_VERSION, // 20
|
||||
FILE_MINOR_VERSION, // 21
|
||||
FILE_BUILD_NUMBER, // 22
|
||||
FILE_REVISION_NUMBER, // 23
|
||||
RETARGET, // 24
|
||||
SIGNATURE_BLOB, // 25
|
||||
CONFIG_MASK, // 26
|
||||
ARCHITECTURE, // 27
|
||||
CONTENT_TYPE, // 28
|
||||
MAX_PARAMS // 29
|
||||
}
|
||||
|
||||
private static class CANOF
|
||||
{
|
||||
public const uint PARSE_DISPLAY_NAME = 0x1;
|
||||
public const uint SET_DEFAULT_VALUES = 0x2;
|
||||
}
|
||||
|
||||
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CD193BC0-B4BC-11d2-9833-00C04FC31D2E")]
|
||||
internal unsafe interface IAssemblyName
|
||||
{
|
||||
void SetProperty(PropertyId id, void* data, uint size);
|
||||
|
||||
[PreserveSig]
|
||||
int GetProperty(PropertyId id, void* data, ref uint size);
|
||||
|
||||
[PreserveSig]
|
||||
int Finalize();
|
||||
|
||||
[PreserveSig]
|
||||
int GetDisplayName(byte* buffer, ref uint characterCount, ASM_DISPLAYF dwDisplayFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int __BindToObject(/*...*/);
|
||||
|
||||
[PreserveSig]
|
||||
int __GetName(/*...*/);
|
||||
|
||||
[PreserveSig]
|
||||
int GetVersion(out uint versionHi, out uint versionLow);
|
||||
|
||||
[PreserveSig]
|
||||
int IsEqual(IAssemblyName pName, uint dwCmpFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int Clone(out IAssemblyName pName);
|
||||
}
|
||||
|
||||
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("7c23ff90-33af-11d3-95da-00a024a85b51")]
|
||||
internal interface IApplicationContext
|
||||
{
|
||||
}
|
||||
|
||||
// NOTE: The CLR caches assembly identities, but doesn't do so in a threadsafe manner.
|
||||
// Wrap all calls to this with a lock.
|
||||
private static object s_assemblyIdentityGate = new object();
|
||||
private static int CreateAssemblyNameObject(out IAssemblyName ppEnum, string szAssemblyName, uint dwFlags, IntPtr pvReserved)
|
||||
{
|
||||
lock (s_assemblyIdentityGate)
|
||||
{
|
||||
return RealCreateAssemblyNameObject(out ppEnum, szAssemblyName, dwFlags, pvReserved);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("clr", EntryPoint = "CreateAssemblyNameObject", CharSet = CharSet.Unicode, PreserveSig = true)]
|
||||
private static extern int RealCreateAssemblyNameObject(out IAssemblyName ppEnum, [MarshalAs(UnmanagedType.LPWStr)]string szAssemblyName, uint dwFlags, IntPtr pvReserved);
|
||||
|
||||
private const int ERROR_INSUFFICIENT_BUFFER = unchecked((int)0x8007007A);
|
||||
private const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047);
|
||||
|
||||
internal static unsafe string GetDisplayName(IAssemblyName nameObject, ASM_DISPLAYF displayFlags)
|
||||
{
|
||||
int hr;
|
||||
uint characterCountIncludingTerminator = 0;
|
||||
|
||||
hr = nameObject.GetDisplayName(null, ref characterCountIncludingTerminator, displayFlags);
|
||||
if (hr == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (hr != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
byte[] data = new byte[(int)characterCountIncludingTerminator * 2];
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
hr = nameObject.GetDisplayName(p, ref characterCountIncludingTerminator, displayFlags);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
|
||||
return Marshal.PtrToStringUni((IntPtr)p, (int)characterCountIncludingTerminator - 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe byte[] GetPropertyBytes(IAssemblyName nameObject, PropertyId propertyId)
|
||||
{
|
||||
int hr;
|
||||
uint size = 0;
|
||||
|
||||
hr = nameObject.GetProperty(propertyId, null, ref size);
|
||||
if (hr == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hr != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
byte[] data = new byte[(int)size];
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
hr = nameObject.GetProperty(propertyId, p, ref size);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
internal static unsafe string GetPropertyString(IAssemblyName nameObject, PropertyId propertyId)
|
||||
{
|
||||
byte[] data = GetPropertyBytes(nameObject, propertyId);
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
fixed (byte* p = data)
|
||||
{
|
||||
return Marshal.PtrToStringUni((IntPtr)p, (data.Length / 2) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe Version GetVersion(IAssemblyName nameObject)
|
||||
{
|
||||
uint hi, lo;
|
||||
int hr = nameObject.GetVersion(out hi, out lo);
|
||||
if (hr != 0)
|
||||
{
|
||||
Debug.Assert(hr == FUSION_E_INVALID_NAME);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Version((int)(hi >> 16), (int)(hi & 0xffff), (int)(lo >> 16), (int)(lo & 0xffff));
|
||||
}
|
||||
|
||||
internal static unsafe uint? GetPropertyWord(IAssemblyName nameObject, PropertyId propertyId)
|
||||
{
|
||||
uint result;
|
||||
uint size = sizeof(uint);
|
||||
int hr = nameObject.GetProperty(propertyId, &result, ref size);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static string GetCulture(IAssemblyName nameObject)
|
||||
{
|
||||
return GetPropertyString(nameObject, PropertyId.CULTURE);
|
||||
}
|
||||
|
||||
internal static ProcessorArchitecture GetProcessorArchitecture(IAssemblyName nameObject)
|
||||
{
|
||||
return (ProcessorArchitecture)(GetPropertyWord(nameObject, PropertyId.ARCHITECTURE) ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="IAssemblyName"/> object by parsing given display name.
|
||||
/// </summary>
|
||||
internal static IAssemblyName ToAssemblyNameObject(string displayName)
|
||||
{
|
||||
// CLR doesn't handle \0 in the display name well:
|
||||
if (displayName.IndexOf('\0') >= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.Assert(displayName != null);
|
||||
IAssemblyName result;
|
||||
int hr = CreateAssemblyNameObject(out result, displayName, CANOF.PARSE_DISPLAY_NAME, IntPtr.Zero);
|
||||
if (hr != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.Assert(result != null);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the candidate assembly with the largest version number. Uses culture as a tie-breaker if it is provided.
|
||||
/// All candidates are assumed to have the same name and must include versions and cultures.
|
||||
/// </summary>
|
||||
internal static IAssemblyName GetBestMatch(IEnumerable<IAssemblyName> candidates, string preferredCultureOpt)
|
||||
{
|
||||
IAssemblyName bestCandidate = null;
|
||||
Version bestVersion = null;
|
||||
string bestCulture = null;
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (bestCandidate != null)
|
||||
{
|
||||
Version candidateVersion = GetVersion(candidate);
|
||||
Debug.Assert(candidateVersion != null);
|
||||
|
||||
if (bestVersion == null)
|
||||
{
|
||||
bestVersion = GetVersion(bestCandidate);
|
||||
Debug.Assert(bestVersion != null);
|
||||
}
|
||||
|
||||
int cmp = bestVersion.CompareTo(candidateVersion);
|
||||
if (cmp == 0)
|
||||
{
|
||||
if (preferredCultureOpt != null)
|
||||
{
|
||||
string candidateCulture = GetCulture(candidate);
|
||||
Debug.Assert(candidateCulture != null);
|
||||
|
||||
if (bestCulture == null)
|
||||
{
|
||||
bestCulture = GetCulture(candidate);
|
||||
Debug.Assert(bestCulture != null);
|
||||
}
|
||||
|
||||
// we have exactly the preferred culture or
|
||||
// we have neutral culture and the best candidate's culture isn't the preferred one:
|
||||
if (StringComparer.OrdinalIgnoreCase.Equals(candidateCulture, preferredCultureOpt) ||
|
||||
candidateCulture.Length == 0 && !StringComparer.OrdinalIgnoreCase.Equals(bestCulture, preferredCultureOpt))
|
||||
{
|
||||
bestCandidate = candidate;
|
||||
bestVersion = candidateVersion;
|
||||
bestCulture = candidateCulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cmp < 0)
|
||||
{
|
||||
bestCandidate = candidate;
|
||||
bestVersion = candidateVersion;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bestCandidate = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return bestCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !CORECLR
|
||||
@@ -1,215 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Code in this file was copied from https://github.com/dotnet/roslyn
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#if !CORECLR // Only enable/port what is needed by CORE CLR.
|
||||
namespace Microsoft.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache.
|
||||
/// </summary>
|
||||
internal static class GlobalAssemblyCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the current Processor architecture.
|
||||
/// </summary>
|
||||
public static readonly ProcessorArchitecture[] CurrentArchitectures = (IntPtr.Size == 4)
|
||||
? new[] { ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.X86 }
|
||||
|
||||
: new[] { ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.Amd64 };
|
||||
|
||||
#region Interop
|
||||
|
||||
private const int MAX_PATH = 260;
|
||||
|
||||
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")]
|
||||
private interface IAssemblyEnum
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetNextAssembly(out FusionAssemblyIdentity.IApplicationContext ppAppCtx, out FusionAssemblyIdentity.IAssemblyName ppName, uint dwFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int Reset();
|
||||
|
||||
[PreserveSig]
|
||||
int Clone(out IAssemblyEnum ppEnum);
|
||||
}
|
||||
|
||||
[ComImport, Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IAssemblyCache
|
||||
{
|
||||
void UninstallAssembly();
|
||||
|
||||
void QueryAssemblyInfo(uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref ASSEMBLY_INFO pAsmInfo);
|
||||
|
||||
void CreateAssemblyCacheItem();
|
||||
void CreateAssemblyScavenger();
|
||||
void InstallAssembly();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private unsafe struct ASSEMBLY_INFO
|
||||
{
|
||||
public uint cbAssemblyInfo;
|
||||
public uint dwAssemblyFlags;
|
||||
public ulong uliAssemblySizeInKB;
|
||||
public char* pszCurrentAssemblyPathBuf;
|
||||
public uint cchBuf;
|
||||
}
|
||||
|
||||
private enum ASM_CACHE
|
||||
{
|
||||
ZAP = 0x1,
|
||||
GAC = 0x2, // C:\Windows\Assembly\GAC
|
||||
DOWNLOAD = 0x4,
|
||||
ROOT = 0x8, // C:\Windows\Assembly
|
||||
GAC_MSIL = 0x10,
|
||||
GAC_32 = 0x20, // C:\Windows\Assembly\GAC_32
|
||||
GAC_64 = 0x40, // C:\Windows\Assembly\GAC_64
|
||||
ROOT_EX = 0x80, // C:\Windows\Microsoft.NET\assembly
|
||||
}
|
||||
|
||||
[DllImport("clr", CharSet = CharSet.Auto, PreserveSig = true)]
|
||||
private static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum, FusionAssemblyIdentity.IApplicationContext pAppCtx, FusionAssemblyIdentity.IAssemblyName pName, ASM_CACHE dwFlags, IntPtr pvReserved);
|
||||
|
||||
[DllImport("clr", CharSet = CharSet.Auto, PreserveSig = false)]
|
||||
private static extern void CreateAssemblyCache(out IAssemblyCache ppAsmCache, uint dwReserved);
|
||||
|
||||
#endregion
|
||||
|
||||
private const int S_OK = 0;
|
||||
private const int S_FALSE = 1;
|
||||
|
||||
// Internal for testing.
|
||||
internal static IEnumerable<FusionAssemblyIdentity.IAssemblyName> GetAssemblyObjects(
|
||||
FusionAssemblyIdentity.IAssemblyName partialNameFilter,
|
||||
ProcessorArchitecture[] architectureFilter)
|
||||
{
|
||||
IAssemblyEnum enumerator;
|
||||
|
||||
int hr = CreateAssemblyEnum(out enumerator, null, partialNameFilter, ASM_CACHE.GAC, IntPtr.Zero);
|
||||
if (hr == S_FALSE)
|
||||
{
|
||||
// no assembly found
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (hr != S_OK)
|
||||
{
|
||||
Exception e = Marshal.GetExceptionForHR(hr);
|
||||
if (e is FileNotFoundException)
|
||||
{
|
||||
// invalid assembly name:
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (e != null)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
// for some reason it might happen that CreateAssemblyEnum returns non-zero HR that doesn't correspond to any exception:
|
||||
throw new ArgumentException("Invalid assembly name");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
FusionAssemblyIdentity.IAssemblyName nameObject;
|
||||
|
||||
FusionAssemblyIdentity.IApplicationContext applicationContext;
|
||||
hr = enumerator.GetNextAssembly(out applicationContext, out nameObject, 0);
|
||||
if (hr != 0)
|
||||
{
|
||||
if (hr < 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (architectureFilter != null)
|
||||
{
|
||||
var assemblyArchitecture = FusionAssemblyIdentity.GetProcessorArchitecture(nameObject);
|
||||
if (!architectureFilter.Contains(assemblyArchitecture))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
yield return nameObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up specified partial assembly name in the GAC and returns the best matching full assembly name/>.
|
||||
/// </summary>
|
||||
/// <param name="displayName">The display name of an assembly.</param>
|
||||
/// <param name="location">Full path name of the resolved assembly.</param>
|
||||
/// <param name="architectureFilter">The optional processor architecture.</param>
|
||||
/// <param name="preferredCulture">The optional preferred culture information.</param>
|
||||
/// <returns>An assembly identity or null, if <paramref name="displayName"/> can't be resolved.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="displayName"/> is null.</exception>
|
||||
public static unsafe string ResolvePartialName(
|
||||
string displayName,
|
||||
out string location,
|
||||
ProcessorArchitecture[] architectureFilter = null,
|
||||
CultureInfo preferredCulture = null)
|
||||
{
|
||||
if (displayName == null)
|
||||
{
|
||||
throw new ArgumentNullException("displayName");
|
||||
}
|
||||
|
||||
location = null;
|
||||
FusionAssemblyIdentity.IAssemblyName nameObject = FusionAssemblyIdentity.ToAssemblyNameObject(displayName);
|
||||
if (nameObject == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidates = GetAssemblyObjects(nameObject, architectureFilter);
|
||||
string cultureName = (preferredCulture != null && !preferredCulture.IsNeutralCulture) ? preferredCulture.Name : null;
|
||||
|
||||
var bestMatch = FusionAssemblyIdentity.GetBestMatch(candidates, cultureName);
|
||||
if (bestMatch == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string fullName = FusionAssemblyIdentity.GetDisplayName(bestMatch, FusionAssemblyIdentity.ASM_DISPLAYF.FULL);
|
||||
|
||||
fixed (char* p = new char[MAX_PATH])
|
||||
{
|
||||
ASSEMBLY_INFO info = new ASSEMBLY_INFO
|
||||
{
|
||||
cbAssemblyInfo = (uint)Marshal.SizeOf(typeof(ASSEMBLY_INFO)),
|
||||
pszCurrentAssemblyPathBuf = p,
|
||||
cchBuf = (uint)MAX_PATH
|
||||
};
|
||||
|
||||
IAssemblyCache assemblyCacheObject;
|
||||
CreateAssemblyCache(out assemblyCacheObject, 0);
|
||||
assemblyCacheObject.QueryAssemblyInfo(0, fullName, ref info);
|
||||
Debug.Assert(info.pszCurrentAssemblyPathBuf != null);
|
||||
Debug.Assert(info.pszCurrentAssemblyPathBuf[info.cchBuf - 1] == '\0');
|
||||
|
||||
var result = Marshal.PtrToStringUni((IntPtr)info.pszCurrentAssemblyPathBuf, (int)info.cchBuf - 1);
|
||||
Debug.Assert(result.IndexOf('\0') == -1);
|
||||
location = result;
|
||||
}
|
||||
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !CORECLR
|
||||
@@ -5115,13 +5115,6 @@ namespace System.Management.Automation.Language
|
||||
|
||||
assemblyFileName = workingDirectory + @"\" + assemblyFileName;
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
if (!File.Exists(assemblyFileName))
|
||||
{
|
||||
GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user